Imported Upstream version 20210313 upstream upstream/20210313
authorJinWang An <jinwang.an@samsung.com>
Tue, 30 Mar 2021 06:03:51 +0000 (15:03 +0900)
committerJinWang An <jinwang.an@samsung.com>
Tue, 30 Mar 2021 06:03:51 +0000 (15:03 +0900)
805 files changed:
.clang-format [new file with mode: 0644]
.editorconfig [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.style.yapf [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
LICENSE [new file with mode: 0644]
OWNERS [new file with mode: 0644]
README.md [new file with mode: 0644]
build/build_aix.ninja.template [new file with mode: 0644]
build/build_haiku.ninja.template [new file with mode: 0644]
build/build_linux.ninja.template [new file with mode: 0644]
build/build_mac.ninja.template [new file with mode: 0644]
build/build_openbsd.ninja.template [new file with mode: 0644]
build/build_win.ninja.template [new file with mode: 0644]
build/full_test.py [new file with mode: 0755]
build/gen.py [new file with mode: 0755]
docs/cross_compiles.md [new file with mode: 0644]
docs/faq.md [new file with mode: 0644]
docs/language.md [new file with mode: 0644]
docs/quick_start.md [new file with mode: 0644]
docs/reference.md [new file with mode: 0644]
docs/standalone.md [new file with mode: 0644]
docs/style_guide.md [new file with mode: 0644]
examples/ios/.gitignore [new file with mode: 0644]
examples/ios/.gn [new file with mode: 0644]
examples/ios/BUILD.gn [new file with mode: 0644]
examples/ios/app/AppDelegate.h [new file with mode: 0644]
examples/ios/app/AppDelegate.m [new file with mode: 0644]
examples/ios/app/BUILD.gn [new file with mode: 0644]
examples/ios/app/Bar.swift [new file with mode: 0644]
examples/ios/app/Baz.swift [new file with mode: 0644]
examples/ios/app/Foo-Bridging-Header.h [new file with mode: 0644]
examples/ios/app/Foo.swift [new file with mode: 0644]
examples/ios/app/FooWrapper.swift [new file with mode: 0644]
examples/ios/app/SceneDelegate.h [new file with mode: 0644]
examples/ios/app/SceneDelegate.m [new file with mode: 0644]
examples/ios/app/ViewController.h [new file with mode: 0644]
examples/ios/app/ViewController.m [new file with mode: 0644]
examples/ios/app/main.m [new file with mode: 0644]
examples/ios/app/resources/Info.plist [new file with mode: 0644]
examples/ios/app/resources/LaunchScreen.storyboard [new file with mode: 0644]
examples/ios/app/resources/Main.storyboard [new file with mode: 0644]
examples/ios/build/BUILD.gn [new file with mode: 0644]
examples/ios/build/BUILDCONFIG.gn [new file with mode: 0644]
examples/ios/build/config/ios/BUILD.gn [new file with mode: 0644]
examples/ios/build/config/ios/bundle_identifier_prefix.gni [new file with mode: 0644]
examples/ios/build/config/ios/deployment_target.gni [new file with mode: 0644]
examples/ios/build/config/ios/resources/Entitlements-Simulated.plist [new file with mode: 0644]
examples/ios/build/config/ios/resources/Info.plist [new file with mode: 0644]
examples/ios/build/config/ios/resources/compiler-Info.plist [new file with mode: 0644]
examples/ios/build/config/ios/scripts/compile_storyboard.py [new file with mode: 0644]
examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py [new file with mode: 0644]
examples/ios/build/config/ios/scripts/generate_umbrella_header.py [new file with mode: 0644]
examples/ios/build/config/ios/scripts/merge_plist.py [new file with mode: 0644]
examples/ios/build/config/ios/scripts/sdk_info.py [new file with mode: 0644]
examples/ios/build/config/ios/sdk_info.gni [new file with mode: 0644]
examples/ios/build/config/ios/templates/ios_app_bundle.gni [new file with mode: 0644]
examples/ios/build/config/ios/templates/ios_binary_bundle.gni [new file with mode: 0644]
examples/ios/build/config/ios/templates/ios_framework_bundle.gni [new file with mode: 0644]
examples/ios/build/config/ios/templates/merge_plist.gni [new file with mode: 0644]
examples/ios/build/config/ios/templates/storyboards.gni [new file with mode: 0644]
examples/ios/build/toolchain/apple/swiftc.py [new file with mode: 0755]
examples/ios/build/toolchain/ios/BUILD.gn [new file with mode: 0644]
examples/ios/build/toolchain/mac/BUILD.gn [new file with mode: 0644]
examples/ios/host/BUILD.gn [new file with mode: 0644]
examples/ios/host/main.cc [new file with mode: 0644]
examples/ios/shared/BUILD.gn [new file with mode: 0644]
examples/ios/shared/hello_shared.h [new file with mode: 0644]
examples/ios/shared/hello_shared.m [new file with mode: 0644]
examples/rust_example/.gn [new file with mode: 0644]
examples/rust_example/BUILD.gn [new file with mode: 0644]
examples/rust_example/BUILDCONFIG.gn [new file with mode: 0644]
examples/rust_example/README.txt [new file with mode: 0644]
examples/rust_example/build/BUILD.gn [new file with mode: 0644]
examples/rust_example/hello_world/bar/src/BUILD.gn [new file with mode: 0644]
examples/rust_example/hello_world/bar/src/lib.rs [new file with mode: 0644]
examples/rust_example/hello_world/foo/src/BUILD.gn [new file with mode: 0644]
examples/rust_example/hello_world/foo/src/lib.rs [new file with mode: 0644]
examples/rust_example/hello_world/src/BUILD.gn [new file with mode: 0644]
examples/rust_example/hello_world/src/main.rs [new file with mode: 0644]
examples/simple_build/.gn [new file with mode: 0644]
examples/simple_build/BUILD.gn [new file with mode: 0644]
examples/simple_build/README.md [new file with mode: 0644]
examples/simple_build/build/BUILD.gn [new file with mode: 0644]
examples/simple_build/build/BUILDCONFIG.gn [new file with mode: 0644]
examples/simple_build/build/toolchain/BUILD.gn [new file with mode: 0644]
examples/simple_build/hello.cc [new file with mode: 0644]
examples/simple_build/hello_shared.cc [new file with mode: 0644]
examples/simple_build/hello_shared.h [new file with mode: 0644]
examples/simple_build/hello_static.cc [new file with mode: 0644]
examples/simple_build/hello_static.h [new file with mode: 0644]
examples/simple_build/tutorial/README.md [new file with mode: 0644]
examples/simple_build/tutorial/tutorial.cc [new file with mode: 0644]
infra/README.recipes.md [new file with mode: 0644]
infra/config/recipes.cfg [new file with mode: 0644]
infra/config/refs.cfg [new file with mode: 0644]
infra/recipe_modules/macos_sdk/__init__.py [new file with mode: 0644]
infra/recipe_modules/macos_sdk/api.py [new file with mode: 0644]
infra/recipe_modules/macos_sdk/examples/full.expected/linux.json [new file with mode: 0644]
infra/recipe_modules/macos_sdk/examples/full.expected/mac.json [new file with mode: 0644]
infra/recipe_modules/macos_sdk/examples/full.expected/win.json [new file with mode: 0644]
infra/recipe_modules/macos_sdk/examples/full.py [new file with mode: 0644]
infra/recipe_modules/target/__init__.py [new file with mode: 0644]
infra/recipe_modules/target/api.py [new file with mode: 0644]
infra/recipe_modules/target/examples/full.expected/linux.json [new file with mode: 0644]
infra/recipe_modules/target/examples/full.expected/mac.json [new file with mode: 0644]
infra/recipe_modules/target/examples/full.expected/win.json [new file with mode: 0644]
infra/recipe_modules/target/examples/full.py [new file with mode: 0644]
infra/recipe_modules/windows_sdk/__init__.py [new file with mode: 0644]
infra/recipe_modules/windows_sdk/api.py [new file with mode: 0644]
infra/recipe_modules/windows_sdk/examples/full.expected/linux.json [new file with mode: 0644]
infra/recipe_modules/windows_sdk/examples/full.expected/mac.json [new file with mode: 0644]
infra/recipe_modules/windows_sdk/examples/full.expected/win.json [new file with mode: 0644]
infra/recipe_modules/windows_sdk/examples/full.py [new file with mode: 0644]
infra/recipes.py [new file with mode: 0755]
infra/recipes/gn.expected/ci_linux.json [new file with mode: 0644]
infra/recipes/gn.expected/ci_mac.json [new file with mode: 0644]
infra/recipes/gn.expected/ci_win.json [new file with mode: 0644]
infra/recipes/gn.expected/cipd_exists.json [new file with mode: 0644]
infra/recipes/gn.expected/cipd_register.json [new file with mode: 0644]
infra/recipes/gn.expected/cq_linux.json [new file with mode: 0644]
infra/recipes/gn.expected/cq_mac.json [new file with mode: 0644]
infra/recipes/gn.expected/cq_win.json [new file with mode: 0644]
infra/recipes/gn.py [new file with mode: 0644]
misc/emacs/.gitignore [new file with mode: 0644]
misc/emacs/gn-mode.el [new file with mode: 0644]
misc/help_as_html.py [new file with mode: 0755]
misc/tm/GN.tmLanguage [new file with mode: 0644]
misc/tm/GN.tmPreferences [new file with mode: 0644]
misc/vim/README.md [new file with mode: 0644]
misc/vim/autoload/gn.vim [new file with mode: 0644]
misc/vim/ftdetect/gnfiletype.vim [new file with mode: 0644]
misc/vim/ftplugin/gn.vim [new file with mode: 0644]
misc/vim/gn-format.py [new file with mode: 0644]
misc/vim/syntax/gn.vim [new file with mode: 0644]
src/base/atomic_ref_count.h [new file with mode: 0644]
src/base/command_line.cc [new file with mode: 0644]
src/base/command_line.h [new file with mode: 0644]
src/base/compiler_specific.h [new file with mode: 0644]
src/base/containers/circular_deque.h [new file with mode: 0644]
src/base/containers/flat_map.h [new file with mode: 0644]
src/base/containers/flat_set.h [new file with mode: 0644]
src/base/containers/flat_tree.h [new file with mode: 0644]
src/base/containers/queue.h [new file with mode: 0644]
src/base/containers/span.h [new file with mode: 0644]
src/base/containers/stack.h [new file with mode: 0644]
src/base/containers/vector_buffer.h [new file with mode: 0644]
src/base/environment.cc [new file with mode: 0644]
src/base/environment.h [new file with mode: 0644]
src/base/files/file.cc [new file with mode: 0644]
src/base/files/file.h [new file with mode: 0644]
src/base/files/file_enumerator.cc [new file with mode: 0644]
src/base/files/file_enumerator.h [new file with mode: 0644]
src/base/files/file_enumerator_posix.cc [new file with mode: 0644]
src/base/files/file_enumerator_win.cc [new file with mode: 0644]
src/base/files/file_path.cc [new file with mode: 0644]
src/base/files/file_path.h [new file with mode: 0644]
src/base/files/file_path_constants.cc [new file with mode: 0644]
src/base/files/file_posix.cc [new file with mode: 0644]
src/base/files/file_util.cc [new file with mode: 0644]
src/base/files/file_util.h [new file with mode: 0644]
src/base/files/file_util_linux.cc [new file with mode: 0644]
src/base/files/file_util_posix.cc [new file with mode: 0644]
src/base/files/file_util_win.cc [new file with mode: 0644]
src/base/files/file_win.cc [new file with mode: 0644]
src/base/files/platform_file.h [new file with mode: 0644]
src/base/files/scoped_file.cc [new file with mode: 0644]
src/base/files/scoped_file.h [new file with mode: 0644]
src/base/files/scoped_temp_dir.cc [new file with mode: 0644]
src/base/files/scoped_temp_dir.h [new file with mode: 0644]
src/base/gtest_prod_util.h [new file with mode: 0644]
src/base/json/json_parser.cc [new file with mode: 0644]
src/base/json/json_parser.h [new file with mode: 0644]
src/base/json/json_reader.cc [new file with mode: 0644]
src/base/json/json_reader.h [new file with mode: 0644]
src/base/json/json_value_converter.cc [new file with mode: 0644]
src/base/json/json_value_converter.h [new file with mode: 0644]
src/base/json/json_writer.cc [new file with mode: 0644]
src/base/json/json_writer.h [new file with mode: 0644]
src/base/json/string_escape.cc [new file with mode: 0644]
src/base/json/string_escape.h [new file with mode: 0644]
src/base/logging.cc [new file with mode: 0644]
src/base/logging.h [new file with mode: 0644]
src/base/mac/bundle_locations.h [new file with mode: 0644]
src/base/mac/mac_logging.h [new file with mode: 0644]
src/base/mac/mac_logging.mm [new file with mode: 0644]
src/base/mac/scoped_cftyperef.h [new file with mode: 0644]
src/base/mac/scoped_typeref.h [new file with mode: 0644]
src/base/macros.h [new file with mode: 0644]
src/base/md5.cc [new file with mode: 0644]
src/base/md5.h [new file with mode: 0644]
src/base/memory/free_deleter.h [new file with mode: 0644]
src/base/memory/ptr_util.h [new file with mode: 0644]
src/base/memory/raw_scoped_refptr_mismatch_checker.h [new file with mode: 0644]
src/base/memory/ref_counted.cc [new file with mode: 0644]
src/base/memory/ref_counted.h [new file with mode: 0644]
src/base/memory/scoped_policy.h [new file with mode: 0644]
src/base/memory/scoped_refptr.h [new file with mode: 0644]
src/base/memory/weak_ptr.cc [new file with mode: 0644]
src/base/memory/weak_ptr.h [new file with mode: 0644]
src/base/numerics/checked_math.h [new file with mode: 0644]
src/base/numerics/checked_math_impl.h [new file with mode: 0644]
src/base/numerics/clamped_math.h [new file with mode: 0644]
src/base/numerics/clamped_math_impl.h [new file with mode: 0644]
src/base/numerics/math_constants.h [new file with mode: 0644]
src/base/numerics/ranges.h [new file with mode: 0644]
src/base/numerics/safe_conversions.h [new file with mode: 0644]
src/base/numerics/safe_conversions_impl.h [new file with mode: 0644]
src/base/numerics/safe_math.h [new file with mode: 0644]
src/base/numerics/safe_math_clang_gcc_impl.h [new file with mode: 0644]
src/base/numerics/safe_math_shared_impl.h [new file with mode: 0644]
src/base/posix/eintr_wrapper.h [new file with mode: 0644]
src/base/posix/file_descriptor_shuffle.cc [new file with mode: 0644]
src/base/posix/file_descriptor_shuffle.h [new file with mode: 0644]
src/base/posix/safe_strerror.cc [new file with mode: 0644]
src/base/posix/safe_strerror.h [new file with mode: 0644]
src/base/scoped_clear_errno.h [new file with mode: 0644]
src/base/scoped_generic.h [new file with mode: 0644]
src/base/sha1.cc [new file with mode: 0644]
src/base/sha1.h [new file with mode: 0644]
src/base/stl_util.h [new file with mode: 0644]
src/base/strings/string_number_conversions.cc [new file with mode: 0644]
src/base/strings/string_number_conversions.h [new file with mode: 0644]
src/base/strings/string_split.cc [new file with mode: 0644]
src/base/strings/string_split.h [new file with mode: 0644]
src/base/strings/string_tokenizer.h [new file with mode: 0644]
src/base/strings/string_util.cc [new file with mode: 0644]
src/base/strings/string_util.h [new file with mode: 0644]
src/base/strings/string_util_constants.cc [new file with mode: 0644]
src/base/strings/string_util_posix.h [new file with mode: 0644]
src/base/strings/string_util_win.h [new file with mode: 0644]
src/base/strings/stringize_macros.h [new file with mode: 0644]
src/base/strings/stringprintf.cc [new file with mode: 0644]
src/base/strings/stringprintf.h [new file with mode: 0644]
src/base/strings/utf_offset_string_conversions.cc [new file with mode: 0644]
src/base/strings/utf_offset_string_conversions.h [new file with mode: 0644]
src/base/strings/utf_string_conversion_utils.cc [new file with mode: 0644]
src/base/strings/utf_string_conversion_utils.h [new file with mode: 0644]
src/base/strings/utf_string_conversions.cc [new file with mode: 0644]
src/base/strings/utf_string_conversions.h [new file with mode: 0644]
src/base/sys_byteorder.h [new file with mode: 0644]
src/base/template_util.h [new file with mode: 0644]
src/base/third_party/icu/LICENSE [new file with mode: 0644]
src/base/third_party/icu/README.chromium [new file with mode: 0644]
src/base/third_party/icu/icu_utf.cc [new file with mode: 0644]
src/base/third_party/icu/icu_utf.h [new file with mode: 0644]
src/base/timer/elapsed_timer.cc [new file with mode: 0644]
src/base/timer/elapsed_timer.h [new file with mode: 0644]
src/base/value_iterators.cc [new file with mode: 0644]
src/base/value_iterators.h [new file with mode: 0644]
src/base/values.cc [new file with mode: 0644]
src/base/values.h [new file with mode: 0644]
src/base/win/registry.cc [new file with mode: 0644]
src/base/win/registry.h [new file with mode: 0644]
src/base/win/scoped_handle.cc [new file with mode: 0644]
src/base/win/scoped_handle.h [new file with mode: 0644]
src/base/win/scoped_process_information.cc [new file with mode: 0644]
src/base/win/scoped_process_information.h [new file with mode: 0644]
src/base/win/win_util.h [new file with mode: 0644]
src/gn/action_target_generator.cc [new file with mode: 0644]
src/gn/action_target_generator.h [new file with mode: 0644]
src/gn/action_target_generator_unittest.cc [new file with mode: 0644]
src/gn/action_values.cc [new file with mode: 0644]
src/gn/action_values.h [new file with mode: 0644]
src/gn/analyzer.cc [new file with mode: 0644]
src/gn/analyzer.h [new file with mode: 0644]
src/gn/analyzer_unittest.cc [new file with mode: 0644]
src/gn/args.cc [new file with mode: 0644]
src/gn/args.h [new file with mode: 0644]
src/gn/args_unittest.cc [new file with mode: 0644]
src/gn/binary_target_generator.cc [new file with mode: 0644]
src/gn/binary_target_generator.h [new file with mode: 0644]
src/gn/build_settings.cc [new file with mode: 0644]
src/gn/build_settings.h [new file with mode: 0644]
src/gn/builder.cc [new file with mode: 0644]
src/gn/builder.h [new file with mode: 0644]
src/gn/builder_record.cc [new file with mode: 0644]
src/gn/builder_record.h [new file with mode: 0644]
src/gn/builder_unittest.cc [new file with mode: 0644]
src/gn/bundle_data.cc [new file with mode: 0644]
src/gn/bundle_data.h [new file with mode: 0644]
src/gn/bundle_data_target_generator.cc [new file with mode: 0644]
src/gn/bundle_data_target_generator.h [new file with mode: 0644]
src/gn/bundle_file_rule.cc [new file with mode: 0644]
src/gn/bundle_file_rule.h [new file with mode: 0644]
src/gn/c_include_iterator.cc [new file with mode: 0644]
src/gn/c_include_iterator.h [new file with mode: 0644]
src/gn/c_include_iterator_unittest.cc [new file with mode: 0644]
src/gn/c_substitution_type.cc [new file with mode: 0644]
src/gn/c_substitution_type.h [new file with mode: 0644]
src/gn/c_tool.cc [new file with mode: 0644]
src/gn/c_tool.h [new file with mode: 0644]
src/gn/command_analyze.cc [new file with mode: 0644]
src/gn/command_args.cc [new file with mode: 0644]
src/gn/command_check.cc [new file with mode: 0644]
src/gn/command_clean.cc [new file with mode: 0644]
src/gn/command_clean_stale.cc [new file with mode: 0644]
src/gn/command_desc.cc [new file with mode: 0644]
src/gn/command_format.cc [new file with mode: 0644]
src/gn/command_format.h [new file with mode: 0644]
src/gn/command_format_unittest.cc [new file with mode: 0644]
src/gn/command_gen.cc [new file with mode: 0644]
src/gn/command_help.cc [new file with mode: 0644]
src/gn/command_ls.cc [new file with mode: 0644]
src/gn/command_meta.cc [new file with mode: 0644]
src/gn/command_outputs.cc [new file with mode: 0644]
src/gn/command_path.cc [new file with mode: 0644]
src/gn/command_refs.cc [new file with mode: 0644]
src/gn/commands.cc [new file with mode: 0644]
src/gn/commands.h [new file with mode: 0644]
src/gn/commands_unittest.cc [new file with mode: 0644]
src/gn/compile_commands_writer.cc [new file with mode: 0644]
src/gn/compile_commands_writer.h [new file with mode: 0644]
src/gn/compile_commands_writer_unittest.cc [new file with mode: 0644]
src/gn/config.cc [new file with mode: 0644]
src/gn/config.h [new file with mode: 0644]
src/gn/config_unittest.cc [new file with mode: 0644]
src/gn/config_values.cc [new file with mode: 0644]
src/gn/config_values.h [new file with mode: 0644]
src/gn/config_values_extractors.cc [new file with mode: 0644]
src/gn/config_values_extractors.h [new file with mode: 0644]
src/gn/config_values_extractors_unittest.cc [new file with mode: 0644]
src/gn/config_values_generator.cc [new file with mode: 0644]
src/gn/config_values_generator.h [new file with mode: 0644]
src/gn/copy_target_generator.cc [new file with mode: 0644]
src/gn/copy_target_generator.h [new file with mode: 0644]
src/gn/create_bundle_target_generator.cc [new file with mode: 0644]
src/gn/create_bundle_target_generator.h [new file with mode: 0644]
src/gn/deps_iterator.cc [new file with mode: 0644]
src/gn/deps_iterator.h [new file with mode: 0644]
src/gn/desc_builder.cc [new file with mode: 0644]
src/gn/desc_builder.h [new file with mode: 0644]
src/gn/eclipse_writer.cc [new file with mode: 0644]
src/gn/eclipse_writer.h [new file with mode: 0644]
src/gn/err.cc [new file with mode: 0644]
src/gn/err.h [new file with mode: 0644]
src/gn/escape.cc [new file with mode: 0644]
src/gn/escape.h [new file with mode: 0644]
src/gn/escape_unittest.cc [new file with mode: 0644]
src/gn/exec_process.cc [new file with mode: 0644]
src/gn/exec_process.h [new file with mode: 0644]
src/gn/exec_process_unittest.cc [new file with mode: 0644]
src/gn/file_writer.cc [new file with mode: 0644]
src/gn/file_writer.h [new file with mode: 0644]
src/gn/file_writer_unittest.cc [new file with mode: 0644]
src/gn/filesystem_utils.cc [new file with mode: 0644]
src/gn/filesystem_utils.h [new file with mode: 0644]
src/gn/filesystem_utils_unittest.cc [new file with mode: 0644]
src/gn/format_test_data/001.gn [new file with mode: 0644]
src/gn/format_test_data/001.golden [new file with mode: 0644]
src/gn/format_test_data/002.gn [new file with mode: 0644]
src/gn/format_test_data/002.golden [new file with mode: 0644]
src/gn/format_test_data/003.gn [new file with mode: 0644]
src/gn/format_test_data/003.golden [new file with mode: 0644]
src/gn/format_test_data/004.gn [new file with mode: 0644]
src/gn/format_test_data/004.golden [new file with mode: 0644]
src/gn/format_test_data/005.gn [new file with mode: 0644]
src/gn/format_test_data/005.golden [new file with mode: 0644]
src/gn/format_test_data/006.gn [new file with mode: 0644]
src/gn/format_test_data/006.golden [new file with mode: 0644]
src/gn/format_test_data/007.gn [new file with mode: 0644]
src/gn/format_test_data/007.golden [new file with mode: 0644]
src/gn/format_test_data/008.gn [new file with mode: 0644]
src/gn/format_test_data/008.golden [new file with mode: 0644]
src/gn/format_test_data/009.gn [new file with mode: 0644]
src/gn/format_test_data/009.golden [new file with mode: 0644]
src/gn/format_test_data/010.gn [new file with mode: 0644]
src/gn/format_test_data/010.golden [new file with mode: 0644]
src/gn/format_test_data/011.gn [new file with mode: 0644]
src/gn/format_test_data/011.golden [new file with mode: 0644]
src/gn/format_test_data/012.gn [new file with mode: 0644]
src/gn/format_test_data/012.golden [new file with mode: 0644]
src/gn/format_test_data/013.gn [new file with mode: 0644]
src/gn/format_test_data/013.golden [new file with mode: 0644]
src/gn/format_test_data/014.gn [new file with mode: 0644]
src/gn/format_test_data/014.golden [new file with mode: 0644]
src/gn/format_test_data/015.gn [new file with mode: 0644]
src/gn/format_test_data/015.golden [new file with mode: 0644]
src/gn/format_test_data/016.gn [new file with mode: 0644]
src/gn/format_test_data/016.golden [new file with mode: 0644]
src/gn/format_test_data/017.gn [new file with mode: 0644]
src/gn/format_test_data/017.golden [new file with mode: 0644]
src/gn/format_test_data/018.gn [new file with mode: 0644]
src/gn/format_test_data/018.golden [new file with mode: 0644]
src/gn/format_test_data/019.gn [new file with mode: 0644]
src/gn/format_test_data/019.golden [new file with mode: 0644]
src/gn/format_test_data/020.gn [new file with mode: 0644]
src/gn/format_test_data/020.golden [new file with mode: 0644]
src/gn/format_test_data/021.gn [new file with mode: 0644]
src/gn/format_test_data/021.golden [new file with mode: 0644]
src/gn/format_test_data/022.gn [new file with mode: 0644]
src/gn/format_test_data/022.golden [new file with mode: 0644]
src/gn/format_test_data/023.gn [new file with mode: 0644]
src/gn/format_test_data/023.golden [new file with mode: 0644]
src/gn/format_test_data/024.gn [new file with mode: 0644]
src/gn/format_test_data/024.golden [new file with mode: 0644]
src/gn/format_test_data/025.gn [new file with mode: 0644]
src/gn/format_test_data/025.golden [new file with mode: 0644]
src/gn/format_test_data/026.gn [new file with mode: 0644]
src/gn/format_test_data/026.golden [new file with mode: 0644]
src/gn/format_test_data/027.gn [new file with mode: 0644]
src/gn/format_test_data/027.golden [new file with mode: 0644]
src/gn/format_test_data/028.gn [new file with mode: 0644]
src/gn/format_test_data/028.golden [new file with mode: 0644]
src/gn/format_test_data/029.gn [new file with mode: 0644]
src/gn/format_test_data/029.golden [new file with mode: 0644]
src/gn/format_test_data/030.gn [new file with mode: 0644]
src/gn/format_test_data/030.golden [new file with mode: 0644]
src/gn/format_test_data/031.gn [new file with mode: 0644]
src/gn/format_test_data/031.golden [new file with mode: 0644]
src/gn/format_test_data/032.gn [new file with mode: 0644]
src/gn/format_test_data/032.golden [new file with mode: 0644]
src/gn/format_test_data/033.gn [new file with mode: 0644]
src/gn/format_test_data/033.golden [new file with mode: 0644]
src/gn/format_test_data/034.gn [new file with mode: 0644]
src/gn/format_test_data/035.gn [new file with mode: 0644]
src/gn/format_test_data/035.golden [new file with mode: 0644]
src/gn/format_test_data/036.gn [new file with mode: 0644]
src/gn/format_test_data/036.golden [new file with mode: 0644]
src/gn/format_test_data/037.gn [new file with mode: 0644]
src/gn/format_test_data/037.golden [new file with mode: 0644]
src/gn/format_test_data/038.gn [new file with mode: 0644]
src/gn/format_test_data/038.golden [new file with mode: 0644]
src/gn/format_test_data/039.gn [new file with mode: 0644]
src/gn/format_test_data/039.golden [new file with mode: 0644]
src/gn/format_test_data/040.gn [new file with mode: 0644]
src/gn/format_test_data/040.golden [new file with mode: 0644]
src/gn/format_test_data/041.gn [new file with mode: 0644]
src/gn/format_test_data/041.golden [new file with mode: 0644]
src/gn/format_test_data/042.gn [new file with mode: 0644]
src/gn/format_test_data/042.golden [new file with mode: 0644]
src/gn/format_test_data/043.gn [new file with mode: 0644]
src/gn/format_test_data/043.golden [new file with mode: 0644]
src/gn/format_test_data/044.gn [new file with mode: 0644]
src/gn/format_test_data/044.golden [new file with mode: 0644]
src/gn/format_test_data/045.gn [new file with mode: 0644]
src/gn/format_test_data/045.golden [new file with mode: 0644]
src/gn/format_test_data/046.gn [new file with mode: 0644]
src/gn/format_test_data/046.golden [new file with mode: 0644]
src/gn/format_test_data/047.gn [new file with mode: 0644]
src/gn/format_test_data/047.golden [new file with mode: 0644]
src/gn/format_test_data/048.gn [new file with mode: 0644]
src/gn/format_test_data/048.golden [new file with mode: 0644]
src/gn/format_test_data/049.gn [new file with mode: 0644]
src/gn/format_test_data/050.gn [new file with mode: 0644]
src/gn/format_test_data/050.golden [new file with mode: 0644]
src/gn/format_test_data/051.gn [new file with mode: 0644]
src/gn/format_test_data/051.golden [new file with mode: 0644]
src/gn/format_test_data/052.gn [new file with mode: 0644]
src/gn/format_test_data/052.golden [new file with mode: 0644]
src/gn/format_test_data/053.gn [new file with mode: 0644]
src/gn/format_test_data/053.golden [new file with mode: 0644]
src/gn/format_test_data/054.gn [new file with mode: 0644]
src/gn/format_test_data/054.golden [new file with mode: 0644]
src/gn/format_test_data/055.gn [new file with mode: 0644]
src/gn/format_test_data/055.golden [new file with mode: 0644]
src/gn/format_test_data/056.gn [new file with mode: 0644]
src/gn/format_test_data/056.golden [new file with mode: 0644]
src/gn/format_test_data/057.gn [new file with mode: 0644]
src/gn/format_test_data/057.golden [new file with mode: 0644]
src/gn/format_test_data/058.gn [new file with mode: 0644]
src/gn/format_test_data/058.golden [new file with mode: 0644]
src/gn/format_test_data/059.gn [new file with mode: 0644]
src/gn/format_test_data/059.golden [new file with mode: 0644]
src/gn/format_test_data/060.gn [new file with mode: 0644]
src/gn/format_test_data/060.golden [new file with mode: 0644]
src/gn/format_test_data/061.gn [new file with mode: 0644]
src/gn/format_test_data/061.golden [new file with mode: 0644]
src/gn/format_test_data/062.gn [new file with mode: 0644]
src/gn/format_test_data/062.golden [new file with mode: 0644]
src/gn/format_test_data/063.gn [new file with mode: 0644]
src/gn/format_test_data/063.golden [new file with mode: 0644]
src/gn/format_test_data/064.gn [new file with mode: 0644]
src/gn/format_test_data/064.golden [new file with mode: 0644]
src/gn/format_test_data/065.gn [new file with mode: 0644]
src/gn/format_test_data/065.golden [new file with mode: 0644]
src/gn/format_test_data/066.gn [new file with mode: 0644]
src/gn/format_test_data/066.golden [new file with mode: 0644]
src/gn/format_test_data/067.gn [new file with mode: 0644]
src/gn/format_test_data/067.golden [new file with mode: 0644]
src/gn/format_test_data/068.gn [new file with mode: 0644]
src/gn/format_test_data/068.golden [new file with mode: 0644]
src/gn/format_test_data/069.gn [new file with mode: 0644]
src/gn/format_test_data/069.golden [new file with mode: 0644]
src/gn/format_test_data/070.gn [new file with mode: 0644]
src/gn/format_test_data/070.golden [new file with mode: 0644]
src/gn/format_test_data/071.gn [new file with mode: 0644]
src/gn/format_test_data/071.golden [new file with mode: 0644]
src/gn/format_test_data/072.gn [new file with mode: 0644]
src/gn/format_test_data/072.golden [new file with mode: 0644]
src/gn/format_test_data/073.gn [new file with mode: 0644]
src/gn/format_test_data/073.golden [new file with mode: 0644]
src/gn/format_test_data/074.gn [new file with mode: 0644]
src/gn/format_test_data/074.golden [new file with mode: 0644]
src/gn/format_test_data/075.gn [new file with mode: 0644]
src/gn/format_test_data/075.golden [new file with mode: 0644]
src/gn/format_test_data/076.gn [new file with mode: 0644]
src/gn/format_test_data/076.golden [new file with mode: 0644]
src/gn/format_test_data/077.gn [new file with mode: 0644]
src/gn/format_test_data/077.golden [new file with mode: 0644]
src/gn/format_test_data/078.gn [new file with mode: 0644]
src/gn/format_test_data/078.golden [new file with mode: 0644]
src/gn/format_test_data/079.gn [new file with mode: 0644]
src/gn/format_test_data/079.golden [new file with mode: 0644]
src/gn/format_test_data/080.gn [new file with mode: 0644]
src/gn/format_test_data/080.golden [new file with mode: 0644]
src/gn/format_test_data/081.gn [new file with mode: 0644]
src/gn/format_test_data/081.golden [new file with mode: 0644]
src/gn/format_test_data/082.gn [new file with mode: 0644]
src/gn/format_test_data/082.golden [new file with mode: 0644]
src/gn/format_test_data/083.gn [new file with mode: 0644]
src/gn/format_test_data/083.golden [new file with mode: 0644]
src/gn/frameworks_utils.cc [new file with mode: 0644]
src/gn/frameworks_utils.h [new file with mode: 0644]
src/gn/frameworks_utils_unittest.cc [new file with mode: 0644]
src/gn/function_exec_script.cc [new file with mode: 0644]
src/gn/function_filter.cc [new file with mode: 0644]
src/gn/function_filter_unittest.cc [new file with mode: 0644]
src/gn/function_foreach.cc [new file with mode: 0644]
src/gn/function_foreach_unittest.cc [new file with mode: 0644]
src/gn/function_forward_variables_from.cc [new file with mode: 0644]
src/gn/function_forward_variables_from_unittest.cc [new file with mode: 0644]
src/gn/function_get_label_info.cc [new file with mode: 0644]
src/gn/function_get_label_info_unittest.cc [new file with mode: 0644]
src/gn/function_get_path_info.cc [new file with mode: 0644]
src/gn/function_get_path_info_unittest.cc [new file with mode: 0644]
src/gn/function_get_target_outputs.cc [new file with mode: 0644]
src/gn/function_get_target_outputs_unittest.cc [new file with mode: 0644]
src/gn/function_process_file_template.cc [new file with mode: 0644]
src/gn/function_process_file_template_unittest.cc [new file with mode: 0644]
src/gn/function_read_file.cc [new file with mode: 0644]
src/gn/function_rebase_path.cc [new file with mode: 0644]
src/gn/function_rebase_path_unittest.cc [new file with mode: 0644]
src/gn/function_set_default_toolchain.cc [new file with mode: 0644]
src/gn/function_set_defaults.cc [new file with mode: 0644]
src/gn/function_template.cc [new file with mode: 0644]
src/gn/function_template_unittest.cc [new file with mode: 0644]
src/gn/function_toolchain.cc [new file with mode: 0644]
src/gn/function_toolchain_unittest.cc [new file with mode: 0644]
src/gn/function_write_file.cc [new file with mode: 0644]
src/gn/function_write_file_unittest.cc [new file with mode: 0644]
src/gn/functions.cc [new file with mode: 0644]
src/gn/functions.h [new file with mode: 0644]
src/gn/functions_target.cc [new file with mode: 0644]
src/gn/functions_target_rust_unittest.cc [new file with mode: 0644]
src/gn/functions_target_unittest.cc [new file with mode: 0644]
src/gn/functions_unittest.cc [new file with mode: 0644]
src/gn/general_tool.cc [new file with mode: 0644]
src/gn/general_tool.h [new file with mode: 0644]
src/gn/generated_file_target_generator.cc [new file with mode: 0644]
src/gn/generated_file_target_generator.h [new file with mode: 0644]
src/gn/gn_main.cc [new file with mode: 0644]
src/gn/group_target_generator.cc [new file with mode: 0644]
src/gn/group_target_generator.h [new file with mode: 0644]
src/gn/hash_table_base.h [new file with mode: 0644]
src/gn/hash_table_base_unittest.cc [new file with mode: 0644]
src/gn/header_checker.cc [new file with mode: 0644]
src/gn/header_checker.h [new file with mode: 0644]
src/gn/header_checker_unittest.cc [new file with mode: 0644]
src/gn/import_manager.cc [new file with mode: 0644]
src/gn/import_manager.h [new file with mode: 0644]
src/gn/inherited_libraries.cc [new file with mode: 0644]
src/gn/inherited_libraries.h [new file with mode: 0644]
src/gn/inherited_libraries_unittest.cc [new file with mode: 0644]
src/gn/input_conversion.cc [new file with mode: 0644]
src/gn/input_conversion.h [new file with mode: 0644]
src/gn/input_conversion_unittest.cc [new file with mode: 0644]
src/gn/input_file.cc [new file with mode: 0644]
src/gn/input_file.h [new file with mode: 0644]
src/gn/input_file_manager.cc [new file with mode: 0644]
src/gn/input_file_manager.h [new file with mode: 0644]
src/gn/item.cc [new file with mode: 0644]
src/gn/item.h [new file with mode: 0644]
src/gn/json_project_writer.cc [new file with mode: 0644]
src/gn/json_project_writer.h [new file with mode: 0644]
src/gn/json_project_writer_unittest.cc [new file with mode: 0644]
src/gn/label.cc [new file with mode: 0644]
src/gn/label.h [new file with mode: 0644]
src/gn/label_pattern.cc [new file with mode: 0644]
src/gn/label_pattern.h [new file with mode: 0644]
src/gn/label_pattern_unittest.cc [new file with mode: 0644]
src/gn/label_ptr.h [new file with mode: 0644]
src/gn/label_unittest.cc [new file with mode: 0644]
src/gn/lib_file.cc [new file with mode: 0644]
src/gn/lib_file.h [new file with mode: 0644]
src/gn/loader.cc [new file with mode: 0644]
src/gn/loader.h [new file with mode: 0644]
src/gn/loader_unittest.cc [new file with mode: 0644]
src/gn/location.cc [new file with mode: 0644]
src/gn/location.h [new file with mode: 0644]
src/gn/metadata.cc [new file with mode: 0644]
src/gn/metadata.h [new file with mode: 0644]
src/gn/metadata_unittest.cc [new file with mode: 0644]
src/gn/metadata_walk.cc [new file with mode: 0644]
src/gn/metadata_walk.h [new file with mode: 0644]
src/gn/metadata_walk_unittest.cc [new file with mode: 0644]
src/gn/ninja_action_target_writer.cc [new file with mode: 0644]
src/gn/ninja_action_target_writer.h [new file with mode: 0644]
src/gn/ninja_action_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_binary_target_writer.cc [new file with mode: 0644]
src/gn/ninja_binary_target_writer.h [new file with mode: 0644]
src/gn/ninja_binary_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_build_writer.cc [new file with mode: 0644]
src/gn/ninja_build_writer.h [new file with mode: 0644]
src/gn/ninja_build_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_bundle_data_target_writer.cc [new file with mode: 0644]
src/gn/ninja_bundle_data_target_writer.h [new file with mode: 0644]
src/gn/ninja_bundle_data_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_c_binary_target_writer.cc [new file with mode: 0644]
src/gn/ninja_c_binary_target_writer.h [new file with mode: 0644]
src/gn/ninja_c_binary_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_copy_target_writer.cc [new file with mode: 0644]
src/gn/ninja_copy_target_writer.h [new file with mode: 0644]
src/gn/ninja_copy_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_create_bundle_target_writer.cc [new file with mode: 0644]
src/gn/ninja_create_bundle_target_writer.h [new file with mode: 0644]
src/gn/ninja_create_bundle_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_generated_file_target_writer.cc [new file with mode: 0644]
src/gn/ninja_generated_file_target_writer.h [new file with mode: 0644]
src/gn/ninja_generated_file_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_group_target_writer.cc [new file with mode: 0644]
src/gn/ninja_group_target_writer.h [new file with mode: 0644]
src/gn/ninja_group_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_rust_binary_target_writer.cc [new file with mode: 0644]
src/gn/ninja_rust_binary_target_writer.h [new file with mode: 0644]
src/gn/ninja_rust_binary_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_target_command_util.cc [new file with mode: 0644]
src/gn/ninja_target_command_util.h [new file with mode: 0644]
src/gn/ninja_target_command_util_unittest.cc [new file with mode: 0644]
src/gn/ninja_target_writer.cc [new file with mode: 0644]
src/gn/ninja_target_writer.h [new file with mode: 0644]
src/gn/ninja_target_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_toolchain_writer.cc [new file with mode: 0644]
src/gn/ninja_toolchain_writer.h [new file with mode: 0644]
src/gn/ninja_toolchain_writer_unittest.cc [new file with mode: 0644]
src/gn/ninja_tools.cc [new file with mode: 0644]
src/gn/ninja_tools.h [new file with mode: 0644]
src/gn/ninja_utils.cc [new file with mode: 0644]
src/gn/ninja_utils.h [new file with mode: 0644]
src/gn/ninja_writer.cc [new file with mode: 0644]
src/gn/ninja_writer.h [new file with mode: 0644]
src/gn/operators.cc [new file with mode: 0644]
src/gn/operators.h [new file with mode: 0644]
src/gn/operators_unittest.cc [new file with mode: 0644]
src/gn/ordered_set.h [new file with mode: 0644]
src/gn/output_conversion.cc [new file with mode: 0644]
src/gn/output_conversion.h [new file with mode: 0644]
src/gn/output_conversion_unittest.cc [new file with mode: 0644]
src/gn/output_file.cc [new file with mode: 0644]
src/gn/output_file.h [new file with mode: 0644]
src/gn/parse_node_value_adapter.cc [new file with mode: 0644]
src/gn/parse_node_value_adapter.h [new file with mode: 0644]
src/gn/parse_tree.cc [new file with mode: 0644]
src/gn/parse_tree.h [new file with mode: 0644]
src/gn/parse_tree_unittest.cc [new file with mode: 0644]
src/gn/parser.cc [new file with mode: 0644]
src/gn/parser.h [new file with mode: 0644]
src/gn/parser_unittest.cc [new file with mode: 0644]
src/gn/path_output.cc [new file with mode: 0644]
src/gn/path_output.h [new file with mode: 0644]
src/gn/path_output_unittest.cc [new file with mode: 0644]
src/gn/pattern.cc [new file with mode: 0644]
src/gn/pattern.h [new file with mode: 0644]
src/gn/pattern_unittest.cc [new file with mode: 0644]
src/gn/pool.cc [new file with mode: 0644]
src/gn/pool.h [new file with mode: 0644]
src/gn/qt_creator_writer.cc [new file with mode: 0644]
src/gn/qt_creator_writer.h [new file with mode: 0644]
src/gn/runtime_deps.cc [new file with mode: 0644]
src/gn/runtime_deps.h [new file with mode: 0644]
src/gn/runtime_deps_unittest.cc [new file with mode: 0644]
src/gn/rust_project_writer.cc [new file with mode: 0644]
src/gn/rust_project_writer.h [new file with mode: 0644]
src/gn/rust_project_writer_helpers.h [new file with mode: 0644]
src/gn/rust_project_writer_helpers_unittest.cc [new file with mode: 0644]
src/gn/rust_project_writer_unittest.cc [new file with mode: 0644]
src/gn/rust_substitution_type.cc [new file with mode: 0644]
src/gn/rust_substitution_type.h [new file with mode: 0644]
src/gn/rust_tool.cc [new file with mode: 0644]
src/gn/rust_tool.h [new file with mode: 0644]
src/gn/rust_values.cc [new file with mode: 0644]
src/gn/rust_values.h [new file with mode: 0644]
src/gn/rust_values_generator.cc [new file with mode: 0644]
src/gn/rust_values_generator.h [new file with mode: 0644]
src/gn/rust_variables.cc [new file with mode: 0644]
src/gn/rust_variables.h [new file with mode: 0644]
src/gn/scheduler.cc [new file with mode: 0644]
src/gn/scheduler.h [new file with mode: 0644]
src/gn/scope.cc [new file with mode: 0644]
src/gn/scope.h [new file with mode: 0644]
src/gn/scope_per_file_provider.cc [new file with mode: 0644]
src/gn/scope_per_file_provider.h [new file with mode: 0644]
src/gn/scope_per_file_provider_unittest.cc [new file with mode: 0644]
src/gn/scope_unittest.cc [new file with mode: 0644]
src/gn/settings.cc [new file with mode: 0644]
src/gn/settings.h [new file with mode: 0644]
src/gn/setup.cc [new file with mode: 0644]
src/gn/setup.h [new file with mode: 0644]
src/gn/setup_unittest.cc [new file with mode: 0644]
src/gn/source_dir.cc [new file with mode: 0644]
src/gn/source_dir.h [new file with mode: 0644]
src/gn/source_dir_unittest.cc [new file with mode: 0644]
src/gn/source_file.cc [new file with mode: 0644]
src/gn/source_file.h [new file with mode: 0644]
src/gn/source_file_unittest.cc [new file with mode: 0644]
src/gn/standard_out.cc [new file with mode: 0644]
src/gn/standard_out.h [new file with mode: 0644]
src/gn/string_atom.cc [new file with mode: 0644]
src/gn/string_atom.h [new file with mode: 0644]
src/gn/string_atom_unittest.cc [new file with mode: 0644]
src/gn/string_output_buffer.cc [new file with mode: 0644]
src/gn/string_output_buffer.h [new file with mode: 0644]
src/gn/string_output_buffer_unittest.cc [new file with mode: 0644]
src/gn/string_utils.cc [new file with mode: 0644]
src/gn/string_utils.h [new file with mode: 0644]
src/gn/string_utils_unittest.cc [new file with mode: 0644]
src/gn/substitution_list.cc [new file with mode: 0644]
src/gn/substitution_list.h [new file with mode: 0644]
src/gn/substitution_pattern.cc [new file with mode: 0644]
src/gn/substitution_pattern.h [new file with mode: 0644]
src/gn/substitution_pattern_unittest.cc [new file with mode: 0644]
src/gn/substitution_type.cc [new file with mode: 0644]
src/gn/substitution_type.h [new file with mode: 0644]
src/gn/substitution_writer.cc [new file with mode: 0644]
src/gn/substitution_writer.h [new file with mode: 0644]
src/gn/substitution_writer_unittest.cc [new file with mode: 0644]
src/gn/swift_values.cc [new file with mode: 0644]
src/gn/swift_values.h [new file with mode: 0644]
src/gn/swift_values_generator.cc [new file with mode: 0644]
src/gn/swift_values_generator.h [new file with mode: 0644]
src/gn/swift_variables.cc [new file with mode: 0644]
src/gn/swift_variables.h [new file with mode: 0644]
src/gn/switches.cc [new file with mode: 0644]
src/gn/switches.h [new file with mode: 0644]
src/gn/target.cc [new file with mode: 0644]
src/gn/target.h [new file with mode: 0644]
src/gn/target_generator.cc [new file with mode: 0644]
src/gn/target_generator.h [new file with mode: 0644]
src/gn/target_unittest.cc [new file with mode: 0644]
src/gn/template.cc [new file with mode: 0644]
src/gn/template.h [new file with mode: 0644]
src/gn/template_unittest.cc [new file with mode: 0644]
src/gn/test_with_scheduler.cc [new file with mode: 0644]
src/gn/test_with_scheduler.h [new file with mode: 0644]
src/gn/test_with_scope.cc [new file with mode: 0644]
src/gn/test_with_scope.h [new file with mode: 0644]
src/gn/token.cc [new file with mode: 0644]
src/gn/token.h [new file with mode: 0644]
src/gn/tokenizer.cc [new file with mode: 0644]
src/gn/tokenizer.h [new file with mode: 0644]
src/gn/tokenizer_unittest.cc [new file with mode: 0644]
src/gn/tool.cc [new file with mode: 0644]
src/gn/tool.h [new file with mode: 0644]
src/gn/toolchain.cc [new file with mode: 0644]
src/gn/toolchain.h [new file with mode: 0644]
src/gn/trace.cc [new file with mode: 0644]
src/gn/trace.h [new file with mode: 0644]
src/gn/unique_vector.h [new file with mode: 0644]
src/gn/unique_vector_unittest.cc [new file with mode: 0644]
src/gn/value.cc [new file with mode: 0644]
src/gn/value.h [new file with mode: 0644]
src/gn/value_extractors.cc [new file with mode: 0644]
src/gn/value_extractors.h [new file with mode: 0644]
src/gn/value_unittest.cc [new file with mode: 0644]
src/gn/variables.cc [new file with mode: 0644]
src/gn/variables.h [new file with mode: 0644]
src/gn/vector_utils.h [new file with mode: 0644]
src/gn/vector_utils_unittest.cc [new file with mode: 0644]
src/gn/version.cc [new file with mode: 0644]
src/gn/version.h [new file with mode: 0644]
src/gn/version_unittest.cc [new file with mode: 0644]
src/gn/visibility.cc [new file with mode: 0644]
src/gn/visibility.h [new file with mode: 0644]
src/gn/visibility_unittest.cc [new file with mode: 0644]
src/gn/visual_studio_utils.cc [new file with mode: 0644]
src/gn/visual_studio_utils.h [new file with mode: 0644]
src/gn/visual_studio_utils_unittest.cc [new file with mode: 0644]
src/gn/visual_studio_writer.cc [new file with mode: 0644]
src/gn/visual_studio_writer.h [new file with mode: 0644]
src/gn/visual_studio_writer_unittest.cc [new file with mode: 0644]
src/gn/xcode_object.cc [new file with mode: 0644]
src/gn/xcode_object.h [new file with mode: 0644]
src/gn/xcode_object_unittest.cc [new file with mode: 0644]
src/gn/xcode_writer.cc [new file with mode: 0644]
src/gn/xcode_writer.h [new file with mode: 0644]
src/gn/xml_element_writer.cc [new file with mode: 0644]
src/gn/xml_element_writer.h [new file with mode: 0644]
src/gn/xml_element_writer_unittest.cc [new file with mode: 0644]
src/util/auto_reset_event.h [new file with mode: 0644]
src/util/build_config.h [new file with mode: 0644]
src/util/exe_path.cc [new file with mode: 0644]
src/util/exe_path.h [new file with mode: 0644]
src/util/msg_loop.cc [new file with mode: 0644]
src/util/msg_loop.h [new file with mode: 0644]
src/util/semaphore.cc [new file with mode: 0644]
src/util/semaphore.h [new file with mode: 0644]
src/util/sys_info.cc [new file with mode: 0644]
src/util/sys_info.h [new file with mode: 0644]
src/util/test/gn_test.cc [new file with mode: 0644]
src/util/test/test.h [new file with mode: 0644]
src/util/ticks.cc [new file with mode: 0644]
src/util/ticks.h [new file with mode: 0644]
src/util/worker_pool.cc [new file with mode: 0644]
src/util/worker_pool.h [new file with mode: 0644]
tools/find_unreachable.py [new file with mode: 0755]

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..b17a52a
--- /dev/null
@@ -0,0 +1,2 @@
+BasedOnStyle: Chromium
+Standard: Cpp11
diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..2491fdf
--- /dev/null
@@ -0,0 +1,3 @@
+[*.py]
+indent_style = space
+indent_size = 2
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..4c20aff
--- /dev/null
@@ -0,0 +1,62 @@
+*.bak
+*.gypcmd
+*.mk
+*.ncb
+*.opensdf
+*.orig
+*.pdb
+*.props
+*.pyc
+*.pyproj
+*.rules
+*.sdf
+*.sln
+*.sublime-project
+*.sublime-workspace
+*.suo
+*.targets
+*.user
+*.vc.opendb
+*.vcproj
+*.vcxproj
+*.vcxproj.filters
+*.vpj
+*.vpw
+*.vpwhistu
+*.vtg
+*.xcodeproj
+*.xcworkspace
+*.VC.db
+*_proto.xml
+*_proto_cpp.xml
+*~
+!Android.mk
+.*.sw?
+.DS_Store
+.cipd
+.classpath
+.cproject
+.gdb_history
+.gdbinit
+.landmines
+.metadata
+.project
+.pydevproject
+.recipe_deps
+.checkstyle
+cscope.*
+out/
+GPATH
+GRTAGS
+GSYMS
+GTAGS
+Session.vim
+tags
+Thumbs.db
+# Settings directories for eclipse
+/.externalToolBuilders/
+/.settings/
+/.vs/
+# Visual Studio Code
+/.vscode/
+/_out
diff --git a/.style.yapf b/.style.yapf
new file mode 100644 (file)
index 0000000..de0c6a7
--- /dev/null
@@ -0,0 +1,2 @@
+[style]
+based_on_style = chromium
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..2e9eb5a
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,49 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+# See python fnmatch module documentation for more information.
+
+Google Inc. <*@google.com>
+HyperConnect Inc. <*@hpcnt.com>
+IBM Inc. <*@*.ibm.com>
+Loongson Technology Corporation Limited. <*@loongson.cn>
+MIPS Technologies, Inc. <*@mips.com>
+NVIDIA Corporation <*@nvidia.com>
+Opera Software ASA <*@opera.com>
+The Chromium Authors <*@chromium.org>
+Vewd Software AS <*@vewd.com>
+Vivaldi Technologies AS <*@vivaldi.com>
+Yandex LLC <*@yandex-team.ru>
+
+Alexis Menard <alexis.menard@intel.com>
+Alfredo Mazzinghi <mzz.lrd@gmail.com>
+Andrew Boyarshin <andrew.boyarshin@gmail.com>
+Anuj Kumar Sharma <anujk.sharma@samsung.com>
+DanCraft99 <simputest@gmail.com>
+Gergely Nagy <ngg@ngg.hu>
+Ilia K <ki.stfu@gmail.com>
+Ivan Naydonov <samogot@gmail.com>
+Joe Armstrong <joearmstrong334@gmail.com>
+Julien Brianceau <jbriance@cisco.com>
+Kal Conley <kcconley@gmail.com>
+Kamil Rytarowski <krytarowski@gmail.com>
+Martijn Croonen <martijn@martijnc.be>
+Matej Knopp <matej.knopp@gmail.com>
+Michael Gilbert <floppymaster@gmail.com>
+Milko Leporis <milko.leporis@imgtec.com>
+Mohan Reddy <mohan.reddy@samsung.com>
+Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
+Riku Voipio <riku.voipio@linaro.org>
+Saikrishna Arcot <saiarcot895@gmail.com>
+Tim Niederhausen <tim@rnc-ag.de>
+Tomas Popela <tomas.popela@gmail.com>
+Tripta Gupta <tripta.g@samsung.com>
+Wink Saville <wink@saville.com>
+Yuriy Taraday <yorik.sar@gmail.com>
+Oleksandr Motsok <boramaabak@gmail.com>
+Ihor Karavan <ihorkaravan96@gmail.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..a32e00c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/OWNERS b/OWNERS
new file mode 100644 (file)
index 0000000..d78697c
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+brettw@chromium.org
+phosek@chromium.org
+scottmg@chromium.org
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..d6c9118
--- /dev/null
+++ b/README.md
@@ -0,0 +1,205 @@
+# GN
+
+GN is a meta-build system that generates build files for
+[Ninja](https://ninja-build.org).
+
+Related resources:
+
+  * Documentation in [docs/](https://gn.googlesource.com/gn/+/master/docs/). In
+    particular [GN Quick Start
+    guide](https://gn.googlesource.com/gn/+/master/docs/quick_start.md)
+    and the [reference](https://gn.googlesource.com/gn/+/master/docs/reference.md)
+    (the latter is all builtin help converted to a single file).
+  * An introductory [presentation](https://docs.google.com/presentation/d/15Zwb53JcncHfEwHpnG_PoIbbzQ3GQi_cpujYwbpcbZo/edit?usp=sharing).
+  * The [mailing list](https://groups.google.com/a/chromium.org/forum/#!forum/gn-dev).
+
+## What GN is for
+
+GN is currently used as the build system for Chromium, Fuchsia, and related
+projects. Some strengths of GN are:
+
+  * It is designed for large projects and large teams. It scales efficiently to
+    many thousands of build files and tens of thousands of source files.
+
+  * It has a readable, clean syntax. Once a build is set-up, it is generally
+    easy for people with no backround in GN to make basic edits to the build.
+
+  * It is designed for multi-platform projects. It can cleanly express many
+    complicated build variants across different platforms. A single build
+    invocation can target multiple platforms.
+
+  * It supports multiple parallel output directories, each with their own
+    configuration. This allows a developer to maintain builds targeting debug,
+    release, or different platforms in parallel without forced rebuilds when
+    switching.
+
+  * It has a focus on correctness. GN checks for the correct dependencies,
+    inputs, and outputs to the extent possible, and has a number of tools to
+    allow developers to ensure the build evolves as desired (for example, `gn
+    check`, `testonly`, `assert_no_deps`).
+
+  * It has comprehensive build-in help available from the command-line.
+
+Although small projects successfully use GN, the focus on large projects has
+some disadvanages:
+
+  * GN has the goal of being minimally expressive. Although it can be quite
+    flexible, a design goal is to direct members of a large team (who may not
+    have much knowledge about the build) down an easy-to-understand, well-lit
+    path. This isn't necessarily the correct trade-off for smaller projects.
+
+  * The minimal build configuration is relatively heavyweight. There are several
+    files required and the exact way all compilers are linkers are run must be
+    specified in the configuration (see "Examples" below). There is no default
+    compiler configuration.
+
+  * It is not easily composable. GN is designed to compile a single large
+    project with relatively uniform settings and rules. Projects like Chromium
+    do bring together multiple repositories from multiple teams, but the
+    projects must agree on some conventions in the build files to allow this to
+    work.
+
+  * GN is designed with the expectation that the developers building a project
+    want to compile an identical configuration. So while builds can integrate
+    with the user's environment like the CXX and CFLAGS variables if they want,
+    this is not the default and most project's builds do not do this. The result
+    is that many GN projects do not integrate well with other systems like
+    ebuild.
+
+  * There is no simple release scheme (see "Versioning and distribution" below).
+    Projects are expected to manage the version of GN they require. Getting an
+    appropriate GN binary can be a hurdle for new contributors to a project.
+    Since it is relatively uncommon, it can be more difficult to find
+    information and examples.
+
+GN can generate Ninja build files for C, C++, Rust, Objective C, and Swift
+source on most popular platforms. Other languages can be compiled using the
+general "action" rules which are executed by Python or another scripting
+language (Google does this to compile Java and Go). But because this is not as
+clean, generally GN is only used when the bulk of the build is in one of the
+main built-in languages.
+
+## Getting a binary
+
+You can download the latest version of GN binary for
+[Linux](https://chrome-infra-packages.appspot.com/dl/gn/gn/linux-amd64/+/latest),
+[macOS](https://chrome-infra-packages.appspot.com/dl/gn/gn/mac-amd64/+/latest) and
+[Windows](https://chrome-infra-packages.appspot.com/dl/gn/gn/windows-amd64/+/latest)
+from Google's build infrastructure (see "Versioning and distribution" below for
+how this is expected to work).
+
+Alternatively, you can build GN from source with a C++17 compiler:
+
+    git clone https://gn.googlesource.com/gn
+    cd gn
+    python build/gen.py
+    ninja -C out
+    # To run tests:
+    out/gn_unittests
+
+On Windows, it is expected that `cl.exe`, `link.exe`, and `lib.exe` can be found
+in `PATH`, so you'll want to run from a Visual Studio command prompt, or
+similar.
+
+On Linux and Mac, the default compiler is `clang++`, a recent version is
+expected to be found in `PATH`. This can be overridden by setting `CC`, `CXX`,
+and `AR`.
+
+## Examples
+
+There is a simple example in [examples/simple_build](examples/simple_build)
+directory that is a good place to get started with the minimal configuration.
+
+To build and run the simple example with the default gcc compiler:
+
+    cd examples/simple_build
+    ../../out/gn gen -C out
+    ninja -C out
+    ./out/hello
+
+For a maximal configuration see the Chromium setup:
+  * [.gn](https://cs.chromium.org/chromium/src/.gn)
+  * [BUILDCONFIG.gn](https://cs.chromium.org/chromium/src/build/config/BUILDCONFIG.gn)
+  * [Toolchain setup](https://cs.chromium.org/chromium/src/build/toolchain/)
+  * [Compiler setup](https://cs.chromium.org/chromium/src/build/config/compiler/BUILD.gn)
+
+and the Fuchsia setup:
+  * [.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/.gn)
+  * [BUILDCONFIG.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/config/BUILDCONFIG.gn)
+  * [Toolchain setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/toolchain/)
+  * [Compiler setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/config/BUILD.gn)
+
+## Reporting bugs
+
+If you find a bug, you can see if it is known or report it in the [bug
+database](https://bugs.chromium.org/p/gn/issues/list).
+
+## Sending patches
+
+GN uses [Gerrit](https://www.gerritcodereview.com/) for code review. The short
+version of how to patch is:
+
+    Register at https://gn-review.googlesource.com.
+
+    ... edit code ...
+    ninja -C out && out/gn_unittests
+
+Then, to upload a change for review:
+
+    git commit
+    git push origin HEAD:refs/for/master
+
+The first time you do this you'll get an error from the server about a missing
+change-ID. Follow the directions in the error message to install the change-ID
+hook and run `git commit --amend` to apply the hook to the current commit.
+
+When revising a change, use:
+
+    git commit --amend
+    git push origin HEAD:refs/for/master
+
+which will add the new changes to the existing code review, rather than creating
+a new one.
+
+We ask that all contributors
+[sign Google's Contributor License Agreement](https://cla.developers.google.com/)
+(either individual or corporate as appropriate, select 'any other Google
+project').
+
+## Community
+
+You may ask questions and follow along with GN's development on Chromium's
+[gn-dev@](https://groups.google.com/a/chromium.org/forum/#!forum/gn-dev)
+Google Group.
+
+## Versioning and distribution
+
+Most open-source projects are designed to use the developer's computer's current
+toolchain such as compiler, linker, and build tool. But the large
+centrally controlled projects that GN is designed for typically want a more
+hermetic environment. They will ensure that developers are using a specific
+compatible toolchain that is versioned with the code
+
+As a result, GN expects that the project choose the appropriate version of GN
+that will work with each version of the project. There is no "current stable
+version" of GN that is expected to work for all projects.
+
+As a result, the GN developers to not maintain any packages in any of the
+various packaging systems (Debian, RedHat, HomeBrew, etc.). Some of these
+systems to have GN packages, but they are maintained by third parties and you
+should use at your own risk. Instead, we recommend you refer your checkout
+tooling to download binaries for a specific hash from [Google's build
+infrastructure](https://chrome-infra-packages.appspot.com/p/gn/gn) or compile
+your own.
+
+GN does not guarantee the backwards-compatibility of new versions and has no
+branches or versioning scheme beyond the sequence of commits to the master git
+branch (which is expected to be stable).
+
+In practice, however, GN is very backwards-compatible. The core functionality
+has been stable for many years and there is enough GN code at Google alone to
+make non-backwards-compatible changes very difficult, even if they were
+desirable.
+
+There have been discussions about adding a versioning scheme with some
+guarantees about backwards-compatibility, but nothing has yet been implemented.
diff --git a/build/build_aix.ninja.template b/build/build_aix.ninja.template
new file mode 100644 (file)
index 0000000..7d696b0
--- /dev/null
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx -MMD -MF $out.d $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcsT $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out $in $libs $solibs
+  description = LINK $out
diff --git a/build/build_haiku.ninja.template b/build/build_haiku.ninja.template
new file mode 100644 (file)
index 0000000..ab117fb
--- /dev/null
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx -MMD -MF $out.d $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcsT $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out -Wl,--start-group $in $libs -Wl,--end-group $solibs
+  description = LINK $out
diff --git a/build/build_linux.ninja.template b/build/build_linux.ninja.template
new file mode 100644 (file)
index 0000000..ab117fb
--- /dev/null
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx -MMD -MF $out.d $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcsT $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out -Wl,--start-group $in $libs -Wl,--end-group $solibs
+  description = LINK $out
diff --git a/build/build_mac.ninja.template b/build/build_mac.ninja.template
new file mode 100644 (file)
index 0000000..8d75a3c
--- /dev/null
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx -MMD -MF $out.d $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcs $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out $in $solibs $libs
+  description = LINK $out
diff --git a/build/build_openbsd.ninja.template b/build/build_openbsd.ninja.template
new file mode 100644 (file)
index 0000000..8466900
--- /dev/null
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx -MMD -MF $out.d $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcs $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out -Wl,--start-group $in $libs -Wl,--end-group $solibs
+  description = LINK $out
diff --git a/build/build_win.ninja.template b/build/build_win.ninja.template
new file mode 100644 (file)
index 0000000..e2ca186
--- /dev/null
@@ -0,0 +1,12 @@
+rule cxx
+  command = ninja -t msvc -- $cxx /nologo /showIncludes /FC $includes $cflags /c $in /Fo$out
+  description = CXX $out
+  deps = msvc
+
+rule alink_thin
+  command = ninja -t msvc -- $ar /nologo /ignore:4221 $libflags /OUT:$out $in
+  description = LIB $out
+
+rule link
+  command = ninja -t msvc -- $ld /nologo $ldflags /OUT:$out /PDB:$out.pdb $in $solibs $libs
+  description = LINK $out
diff --git a/build/full_test.py b/build/full_test.py
new file mode 100755 (executable)
index 0000000..2095ddd
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import shutil
+import subprocess
+import sys
+import timeit
+
+
+IS_WIN = sys.platform.startswith('win')
+
+
+def RemoveDir(d):
+  if os.path.isdir(d):
+    shutil.rmtree(d)
+
+
+def Trial(gn_path_to_use, save_out_dir=None):
+  bin_path = os.path.join('out', 'gntrial')
+  if not os.path.isdir(bin_path):
+    os.makedirs(bin_path)
+  gn_to_run = os.path.join(bin_path, 'gn' + ('.exe' if IS_WIN else ''))
+  shutil.copy2(gn_path_to_use, gn_to_run)
+  comp_dir = os.path.join('out', 'COMP')
+  subprocess.check_call([gn_to_run, 'gen', comp_dir, '-q', '--check'])
+  if save_out_dir:
+    RemoveDir(save_out_dir)
+    shutil.move(comp_dir, save_out_dir)
+
+
+def main():
+  if len(sys.argv) < 3 or len(sys.argv) > 4:
+    print 'Usage: full_test.py /chrome/tree/at/762a25542878 rel_gn_path [clean]'
+    return 1
+
+  if len(sys.argv) == 4:
+    RemoveDir('out')
+
+  subprocess.check_call([sys.executable, os.path.join('build', 'gen.py')])
+  subprocess.check_call(['ninja', '-C', 'out'])
+  subprocess.check_call([os.path.join('out', 'gn_unittests')])
+  orig_dir = os.getcwd()
+
+  in_chrome_tree_gn = sys.argv[2]
+  our_gn = os.path.join(orig_dir, 'out', 'gn' + ('.exe' if IS_WIN else ''))
+
+  os.chdir(sys.argv[1])
+
+  # Check in-tree vs. ours. Uses:
+  # - Chromium tree at 762a25542878 in argv[1] (this can be off by a bit, but
+  #   is roughly when GN was moved out of the Chrome tree, so matches in case GN
+  #   semantics/ordering change after that.)
+  # - relative path to argv[1] built gn binary in argv[2]
+
+  # First, do a comparison to make sure the output between the two gn binaries
+  # actually matches.
+  print 'Confirming output matches...'
+  dir_a = os.path.join('out', 'a')
+  dir_b = os.path.join('out', 'b')
+  Trial(in_chrome_tree_gn, dir_a)
+  Trial(our_gn, dir_b)
+  subprocess.check_call(['diff', '-r', dir_a, dir_b])
+
+  # Then, some time trials.
+  TRIALS = 5
+  print 'Comparing performance... (takes a while)'
+  time_a = timeit.timeit('Trial("%s")' % in_chrome_tree_gn, number=TRIALS,
+                         setup='from __main__ import Trial')
+  time_b = timeit.timeit('Trial("%s")' % our_gn, number=TRIALS,
+                         setup='from __main__ import Trial')
+  print 'In-tree gn avg: %.3fs' % (time_a / TRIALS)
+  print 'Our gn avg: %.3fs' % (time_b / TRIALS)
+
+  return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/build/gen.py b/build/gen.py
new file mode 100755 (executable)
index 0000000..922bf46
--- /dev/null
@@ -0,0 +1,809 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates build.ninja that will build GN."""
+
+import contextlib
+import errno
+import optparse
+import os
+import platform
+import re
+import subprocess
+import sys
+import tempfile
+
+try:  # py3
+    from shlex import quote as shell_quote
+except ImportError:  # py2
+    from pipes import quote as shell_quote
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+REPO_ROOT = os.path.dirname(SCRIPT_DIR)
+
+
+class Platform(object):
+  """Represents a host/target platform."""
+  def __init__(self, platform):
+    self._platform = platform
+    if self._platform is not None:
+      return
+    self._platform = sys.platform
+    if self._platform.startswith('linux'):
+      self._platform = 'linux'
+    elif self._platform.startswith('darwin'):
+      self._platform = 'darwin'
+    elif self._platform.startswith('mingw'):
+      self._platform = 'mingw'
+    elif self._platform.startswith('msys'):
+      self._platform = 'msys'
+    elif self._platform.startswith('win'):
+      self._platform = 'msvc'
+    elif self._platform.startswith('aix'):
+      self._platform = 'aix'
+    elif self._platform.startswith('fuchsia'):
+      self._platform = 'fuchsia'
+    elif self._platform.startswith('freebsd'):
+      self._platform = 'freebsd'
+    elif self._platform.startswith('netbsd'):
+      self._platform = 'netbsd'
+    elif self._platform.startswith('openbsd'):
+      self._platform = 'openbsd'
+    elif self._platform.startswith('haiku'):
+      self._platform = 'haiku'
+    elif self._platform.startswith('sunos'):
+      self._platform = 'solaris'
+
+  @staticmethod
+  def known_platforms():
+    return ['linux', 'darwin', 'mingw', 'msys', 'msvc', 'aix', 'fuchsia', 'freebsd', 'netbsd', 'openbsd', 'haiku', 'solaris']
+
+  def platform(self):
+    return self._platform
+
+  def is_linux(self):
+    return self._platform == 'linux'
+
+  def is_mingw(self):
+    return self._platform == 'mingw'
+
+  def is_msys(self):
+    return self._platform == 'msys'
+
+  def is_msvc(self):
+    return self._platform == 'msvc'
+
+  def is_windows(self):
+    return self.is_mingw() or self.is_msvc()
+
+  def is_darwin(self):
+    return self._platform == 'darwin'
+
+  def is_aix(self):
+    return self._platform == 'aix'
+
+  def is_haiku(self):
+    return self._platform == 'haiku'
+
+  def is_solaris(self):
+    return self._platform == 'solaris'
+
+  def is_posix(self):
+    return self._platform in ['linux', 'freebsd', 'darwin', 'aix', 'openbsd', 'haiku', 'solaris', 'msys', 'netbsd']
+
+
+def main(argv):
+  parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
+  parser.add_option('-d', '--debug', action='store_true',
+                    help='Do a debug build. Defaults to release build.')
+  parser.add_option('--platform',
+                    help='target platform (' +
+                         '/'.join(Platform.known_platforms()) + ')',
+                    choices=Platform.known_platforms())
+  parser.add_option('--host',
+                    help='host platform (' +
+                         '/'.join(Platform.known_platforms()) + ')',
+                    choices=Platform.known_platforms())
+  parser.add_option('--use-lto', action='store_true',
+                    help='Enable the use of LTO')
+  parser.add_option('--use-icf', action='store_true',
+                    help='Enable the use of Identical Code Folding')
+  parser.add_option('--no-last-commit-position', action='store_true',
+                    help='Do not generate last_commit_position.h.')
+  parser.add_option('--out-path',
+                    help='The path to generate the build files in.')
+  parser.add_option('--no-strip', action='store_true',
+                    help='Don\'t strip release build. Useful for profiling.')
+  parser.add_option('--no-static-libstdc++', action='store_true',
+                    default=False, dest='no_static_libstdcpp',
+                    help='Don\'t link libstdc++ statically')
+  parser.add_option('--link-lib',
+                    action='append',
+                    metavar='LINK_LIB',
+                    default=[],
+                    dest='link_libs',
+                    help=('Add a library to the final executable link. ' +
+                          'LINK_LIB must be the path to a static or shared ' +
+                          'library, or \'-l<name>\' on POSIX systems. Can be ' +
+                          'used multiple times. Useful to link custom malloc ' +
+                          'or cpu profiling libraries.'))
+  options, args = parser.parse_args(argv)
+
+  if args:
+    parser.error('Unrecognized command line arguments: %s.' % ', '.join(args))
+
+  platform = Platform(options.platform)
+  if options.host:
+    host = Platform(options.host)
+  else:
+    host = platform
+
+  out_dir = options.out_path or os.path.join(REPO_ROOT, 'out')
+  if not os.path.isdir(out_dir):
+    os.makedirs(out_dir)
+  if not options.no_last_commit_position:
+    GenerateLastCommitPosition(host,
+                               os.path.join(out_dir, 'last_commit_position.h'))
+  WriteGNNinja(os.path.join(out_dir, 'build.ninja'), platform, host, options)
+  return 0
+
+
+def GenerateLastCommitPosition(host, header):
+  ROOT_TAG = 'initial-commit'
+  describe_output = subprocess.check_output(
+      ['git', 'describe', 'HEAD', '--match', ROOT_TAG], shell=host.is_windows(),
+      cwd=REPO_ROOT)
+  mo = re.match(ROOT_TAG + '-(\d+)-g([0-9a-f]+)', describe_output.decode())
+  if not mo:
+    raise ValueError(
+        'Unexpected output from git describe when generating version header')
+
+  contents = '''// Generated by build/gen.py.
+
+#ifndef OUT_LAST_COMMIT_POSITION_H_
+#define OUT_LAST_COMMIT_POSITION_H_
+
+#define LAST_COMMIT_POSITION_NUM %s
+#define LAST_COMMIT_POSITION "%s (%s)"
+
+#endif  // OUT_LAST_COMMIT_POSITION_H_
+''' % (mo.group(1), mo.group(1), mo.group(2))
+
+  # Only write/touch this file if the commit position has changed.
+  old_contents = ''
+  if os.path.isfile(header):
+    with open(header, 'r') as f:
+      old_contents = f.read()
+
+  if old_contents != contents:
+    with open(header, 'w') as f:
+      f.write(contents)
+
+
+def WriteGenericNinja(path, static_libraries, executables,
+                      cxx, ar, ld, platform, host, options,
+                      cflags=[], ldflags=[], libflags=[],
+                      include_dirs=[], solibs=[]):
+  args = ' -d' if options.debug else ''
+  for link_lib in options.link_libs:
+    args +=  ' --link-lib=' + shell_quote(link_lib)
+
+  ninja_header_lines = [
+    'cxx = ' + cxx,
+    'ar = ' + ar,
+    'ld = ' + ld,
+    '',
+    'rule regen',
+    '  command = %s ../build/gen.py%s' % (sys.executable, args),
+    '  description = Regenerating ninja files',
+    '',
+    'build build.ninja: regen',
+    '  generator = 1',
+    '  depfile = build.ninja.d',
+    '',
+  ]
+
+
+  template_filename = os.path.join(SCRIPT_DIR, {
+      'msvc': 'build_win.ninja.template',
+      'mingw': 'build_linux.ninja.template',
+      'msys': 'build_linux.ninja.template',
+      'darwin': 'build_mac.ninja.template',
+      'linux': 'build_linux.ninja.template',
+      'freebsd': 'build_linux.ninja.template',
+      'aix': 'build_aix.ninja.template',
+      'openbsd': 'build_openbsd.ninja.template',
+      'haiku': 'build_haiku.ninja.template',
+      'solaris': 'build_linux.ninja.template',
+      'netbsd': 'build_linux.ninja.template',
+  }[platform.platform()])
+
+  with open(template_filename) as f:
+    ninja_template = f.read()
+
+  if platform.is_windows():
+    executable_ext = '.exe'
+    library_ext = '.lib'
+    object_ext = '.obj'
+  else:
+    executable_ext = ''
+    library_ext = '.a'
+    object_ext = '.o'
+
+  def escape_path_ninja(path):
+      return path.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
+
+  def src_to_obj(path):
+    return escape_path_ninja('%s' % os.path.splitext(path)[0] + object_ext)
+
+  def library_to_a(library):
+    return '%s%s' % (library, library_ext)
+
+  ninja_lines = []
+  def build_source(src_file, settings):
+    ninja_lines.extend([
+        'build %s: cxx %s' % (src_to_obj(src_file),
+                              escape_path_ninja(
+                                  os.path.relpath(
+                                      os.path.join(REPO_ROOT, src_file),
+                                      os.path.dirname(path)))),
+        '  includes = %s' % ' '.join(
+            ['-I' + escape_path_ninja(dirname) for dirname in include_dirs]),
+        '  cflags = %s' % ' '.join(cflags),
+    ])
+
+  for library, settings in static_libraries.items():
+    for src_file in settings['sources']:
+      build_source(src_file, settings)
+
+    ninja_lines.append('build %s: alink_thin %s' % (
+        library_to_a(library),
+        ' '.join([src_to_obj(src_file) for src_file in settings['sources']])))
+    ninja_lines.append('  libflags = %s' % ' '.join(libflags))
+
+
+  for executable, settings in executables.items():
+    for src_file in settings['sources']:
+      build_source(src_file, settings)
+
+    ninja_lines.extend([
+      'build %s%s: link %s | %s' % (
+          executable, executable_ext,
+          ' '.join([src_to_obj(src_file) for src_file in settings['sources']]),
+          ' '.join([library_to_a(library) for library in settings['libs']])),
+      '  ldflags = %s' % ' '.join(ldflags),
+      '  solibs = %s' % ' '.join(solibs),
+      '  libs = %s' % ' '.join(
+          [library_to_a(library) for library in settings['libs']]),
+    ])
+
+  ninja_lines.append('')  # Make sure the file ends with a newline.
+
+  with open(path, 'w') as f:
+    f.write('\n'.join(ninja_header_lines))
+    f.write(ninja_template)
+    f.write('\n'.join(ninja_lines))
+
+  with open(path + '.d', 'w') as f:
+    f.write('build.ninja: ' +
+            os.path.relpath(os.path.join(SCRIPT_DIR, 'gen.py'),
+                            os.path.dirname(path)) + ' ' +
+            os.path.relpath(template_filename, os.path.dirname(path)) + '\n')
+
+
+def WriteGNNinja(path, platform, host, options):
+  if platform.is_msvc():
+    cxx = os.environ.get('CXX', 'cl.exe')
+    ld = os.environ.get('LD', 'link.exe')
+    ar = os.environ.get('AR', 'lib.exe')
+  elif platform.is_aix():
+    cxx = os.environ.get('CXX', 'g++')
+    ld = os.environ.get('LD', 'g++')
+    ar = os.environ.get('AR', 'ar -X64')
+  elif platform.is_msys() or platform.is_mingw():
+    cxx = os.environ.get('CXX', 'g++')
+    ld = os.environ.get('LD', 'g++')
+    ar = os.environ.get('AR', 'ar')
+  else:
+    cxx = os.environ.get('CXX', 'clang++')
+    ld = cxx
+    ar = os.environ.get('AR', 'ar')
+
+  cflags = os.environ.get('CFLAGS', '').split()
+  cflags += os.environ.get('CXXFLAGS', '').split()
+  ldflags = os.environ.get('LDFLAGS', '').split()
+  libflags = os.environ.get('LIBFLAGS', '').split()
+  include_dirs = [
+      os.path.relpath(os.path.join(REPO_ROOT, 'src'), os.path.dirname(path)),
+      '.',
+  ]
+  libs = []
+
+  if not platform.is_msvc():
+    if options.debug:
+      cflags.extend(['-O0', '-g'])
+    else:
+      cflags.append('-DNDEBUG')
+      cflags.append('-O3')
+      if options.no_strip:
+        cflags.append('-g')
+      ldflags.append('-O3')
+      # Use -fdata-sections and -ffunction-sections to place each function
+      # or data item into its own section so --gc-sections can eliminate any
+      # unused functions and data items.
+      cflags.extend(['-fdata-sections', '-ffunction-sections'])
+      ldflags.extend(['-fdata-sections', '-ffunction-sections'])
+      if platform.is_darwin():
+        ldflags.append('-Wl,-dead_strip')
+      elif not platform.is_aix() and not platform.is_solaris():
+        # Garbage collection is done by default on aix.
+        ldflags.append('-Wl,--gc-sections')
+
+      # Omit all symbol information from the output file.
+      if options.no_strip is None:
+        if platform.is_darwin():
+          ldflags.append('-Wl,-S')
+        elif platform.is_aix():
+          ldflags.append('-Wl,-s')
+        elif platform.is_solaris():
+          ldflags.append('-Wl,--strip-all')
+        else:
+          ldflags.append('-Wl,-strip-all')
+
+      # Enable identical code-folding.
+      if options.use_icf and not platform.is_darwin():
+        ldflags.append('-Wl,--icf=all')
+
+      if options.use_lto:
+        cflags.extend(['-flto', '-fwhole-program-vtables'])
+        ldflags.extend(['-flto', '-fwhole-program-vtables'])
+
+    cflags.extend([
+        '-D_FILE_OFFSET_BITS=64',
+        '-D__STDC_CONSTANT_MACROS', '-D__STDC_FORMAT_MACROS',
+        '-pthread',
+        '-pipe',
+        '-fno-exceptions',
+        '-fno-rtti',
+        '-fdiagnostics-color',
+        '-Wall',
+        '-Wextra',
+        '-Wno-unused-parameter',
+        '-std=c++17'
+    ])
+
+    if platform.is_linux() or platform.is_mingw() or platform.is_msys():
+      ldflags.append('-Wl,--as-needed')
+
+      if not options.no_static_libstdcpp:
+        ldflags.append('-static-libstdc++')
+
+      if platform.is_mingw() or platform.is_msys():
+        cflags.remove('-std=c++17')
+        cflags.extend([
+          '-Wno-deprecated-copy',
+          '-Wno-implicit-fallthrough',
+          '-Wno-redundant-move',
+          '-Wno-unused-variable',
+          '-Wno-format',             # Use of %llx, which is supported by _UCRT, false positive
+          '-Wno-strict-aliasing',    # Dereferencing punned pointer
+          '-Wno-cast-function-type', # Casting FARPROC to RegDeleteKeyExPtr
+          '-std=gnu++17',
+        ])
+      else:
+        # This is needed by libc++.
+        libs.append('-ldl')
+    elif platform.is_darwin():
+      min_mac_version_flag = '-mmacosx-version-min=10.9'
+      cflags.append(min_mac_version_flag)
+      ldflags.append(min_mac_version_flag)
+    elif platform.is_aix():
+      cflags.append('-maix64')
+      ldflags.append('-maix64')
+    elif platform.is_haiku():
+      cflags.append('-fPIC')
+      cflags.extend(['-D_BSD_SOURCE'])
+
+    if platform.is_posix() and not platform.is_haiku():
+      ldflags.append('-pthread')
+
+    if platform.is_mingw() or platform.is_msys():
+      cflags.extend(['-DUNICODE',
+                     '-DNOMINMAX',
+                     '-DWIN32_LEAN_AND_MEAN',
+                     '-DWINVER=0x0A00',
+                     '-D_CRT_SECURE_NO_DEPRECATE',
+                     '-D_SCL_SECURE_NO_DEPRECATE',
+                     '-D_UNICODE',
+                     '-D_WIN32_WINNT=0x0A00',
+                     '-D_HAS_EXCEPTIONS=0'
+      ])
+  elif platform.is_msvc():
+    if not options.debug:
+      cflags.extend(['/O2', '/DNDEBUG', '/Zc:inline'])
+      ldflags.extend(['/OPT:REF'])
+
+      if options.use_icf:
+        libflags.extend(['/OPT:ICF'])
+      if options.use_lto:
+        cflags.extend(['/GL'])
+        libflags.extend(['/LTCG'])
+        ldflags.extend(['/LTCG'])
+
+    cflags.extend([
+        '/DNOMINMAX',
+        '/DUNICODE',
+        '/DWIN32_LEAN_AND_MEAN',
+        '/DWINVER=0x0A00',
+        '/D_CRT_SECURE_NO_DEPRECATE',
+        '/D_SCL_SECURE_NO_DEPRECATE',
+        '/D_UNICODE',
+        '/D_WIN32_WINNT=0x0A00',
+        '/FS',
+        '/W4',
+        '/WX',
+        '/Zi',
+        '/wd4099',
+        '/wd4100',
+        '/wd4127',
+        '/wd4244',
+        '/wd4267',
+        '/wd4505',
+        '/wd4838',
+        '/wd4996',
+        '/std:c++17',
+        '/GR-',
+        '/D_HAS_EXCEPTIONS=0',
+    ])
+
+    ldflags.extend(['/DEBUG', '/MACHINE:x64'])
+
+  static_libraries = {
+      'base': {'sources': [
+        'src/base/command_line.cc',
+        'src/base/environment.cc',
+        'src/base/files/file.cc',
+        'src/base/files/file_enumerator.cc',
+        'src/base/files/file_path.cc',
+        'src/base/files/file_path_constants.cc',
+        'src/base/files/file_util.cc',
+        'src/base/files/scoped_file.cc',
+        'src/base/files/scoped_temp_dir.cc',
+        'src/base/json/json_parser.cc',
+        'src/base/json/json_reader.cc',
+        'src/base/json/json_writer.cc',
+        'src/base/json/string_escape.cc',
+        'src/base/logging.cc',
+        'src/base/md5.cc',
+        'src/base/memory/ref_counted.cc',
+        'src/base/memory/weak_ptr.cc',
+        'src/base/sha1.cc',
+        'src/base/strings/string_number_conversions.cc',
+        'src/base/strings/string_split.cc',
+        'src/base/strings/string_util.cc',
+        'src/base/strings/string_util_constants.cc',
+        'src/base/strings/stringprintf.cc',
+        'src/base/strings/utf_string_conversion_utils.cc',
+        'src/base/strings/utf_string_conversions.cc',
+        'src/base/third_party/icu/icu_utf.cc',
+        'src/base/timer/elapsed_timer.cc',
+        'src/base/value_iterators.cc',
+        'src/base/values.cc',
+      ]},
+      'gn_lib': {'sources': [
+        'src/gn/action_target_generator.cc',
+        'src/gn/action_values.cc',
+        'src/gn/analyzer.cc',
+        'src/gn/args.cc',
+        'src/gn/binary_target_generator.cc',
+        'src/gn/build_settings.cc',
+        'src/gn/builder.cc',
+        'src/gn/builder_record.cc',
+        'src/gn/bundle_data.cc',
+        'src/gn/bundle_data_target_generator.cc',
+        'src/gn/bundle_file_rule.cc',
+        'src/gn/c_include_iterator.cc',
+        'src/gn/c_substitution_type.cc',
+        'src/gn/c_tool.cc',
+        'src/gn/command_analyze.cc',
+        'src/gn/command_args.cc',
+        'src/gn/command_check.cc',
+        'src/gn/command_clean.cc',
+        'src/gn/command_clean_stale.cc',
+        'src/gn/command_desc.cc',
+        'src/gn/command_format.cc',
+        'src/gn/command_gen.cc',
+        'src/gn/command_help.cc',
+        'src/gn/command_ls.cc',
+        'src/gn/command_meta.cc',
+        'src/gn/command_outputs.cc',
+        'src/gn/command_path.cc',
+        'src/gn/command_refs.cc',
+        'src/gn/commands.cc',
+        'src/gn/compile_commands_writer.cc',
+        'src/gn/rust_project_writer.cc',
+        'src/gn/config.cc',
+        'src/gn/config_values.cc',
+        'src/gn/config_values_extractors.cc',
+        'src/gn/config_values_generator.cc',
+        'src/gn/copy_target_generator.cc',
+        'src/gn/create_bundle_target_generator.cc',
+        'src/gn/deps_iterator.cc',
+        'src/gn/desc_builder.cc',
+        'src/gn/eclipse_writer.cc',
+        'src/gn/err.cc',
+        'src/gn/escape.cc',
+        'src/gn/exec_process.cc',
+        'src/gn/filesystem_utils.cc',
+        'src/gn/file_writer.cc',
+        'src/gn/frameworks_utils.cc',
+        'src/gn/function_exec_script.cc',
+        'src/gn/function_filter.cc',
+        'src/gn/function_foreach.cc',
+        'src/gn/function_forward_variables_from.cc',
+        'src/gn/function_get_label_info.cc',
+        'src/gn/function_get_path_info.cc',
+        'src/gn/function_get_target_outputs.cc',
+        'src/gn/function_process_file_template.cc',
+        'src/gn/function_read_file.cc',
+        'src/gn/function_rebase_path.cc',
+        'src/gn/function_set_default_toolchain.cc',
+        'src/gn/function_set_defaults.cc',
+        'src/gn/function_template.cc',
+        'src/gn/function_toolchain.cc',
+        'src/gn/function_write_file.cc',
+        'src/gn/functions.cc',
+        'src/gn/functions_target.cc',
+        'src/gn/general_tool.cc',
+        'src/gn/generated_file_target_generator.cc',
+        'src/gn/group_target_generator.cc',
+        'src/gn/header_checker.cc',
+        'src/gn/import_manager.cc',
+        'src/gn/inherited_libraries.cc',
+        'src/gn/input_conversion.cc',
+        'src/gn/input_file.cc',
+        'src/gn/input_file_manager.cc',
+        'src/gn/item.cc',
+        'src/gn/json_project_writer.cc',
+        'src/gn/label.cc',
+        'src/gn/label_pattern.cc',
+        'src/gn/lib_file.cc',
+        'src/gn/loader.cc',
+        'src/gn/location.cc',
+        'src/gn/metadata.cc',
+        'src/gn/metadata_walk.cc',
+        'src/gn/ninja_action_target_writer.cc',
+        'src/gn/ninja_binary_target_writer.cc',
+        'src/gn/ninja_build_writer.cc',
+        'src/gn/ninja_bundle_data_target_writer.cc',
+        'src/gn/ninja_c_binary_target_writer.cc',
+        'src/gn/ninja_copy_target_writer.cc',
+        'src/gn/ninja_create_bundle_target_writer.cc',
+        'src/gn/ninja_generated_file_target_writer.cc',
+        'src/gn/ninja_group_target_writer.cc',
+        'src/gn/ninja_rust_binary_target_writer.cc',
+        'src/gn/ninja_target_command_util.cc',
+        'src/gn/ninja_target_writer.cc',
+        'src/gn/ninja_toolchain_writer.cc',
+        'src/gn/ninja_tools.cc',
+        'src/gn/ninja_utils.cc',
+        'src/gn/ninja_writer.cc',
+        'src/gn/operators.cc',
+        'src/gn/output_conversion.cc',
+        'src/gn/output_file.cc',
+        'src/gn/parse_node_value_adapter.cc',
+        'src/gn/parse_tree.cc',
+        'src/gn/parser.cc',
+        'src/gn/path_output.cc',
+        'src/gn/pattern.cc',
+        'src/gn/pool.cc',
+        'src/gn/qt_creator_writer.cc',
+        'src/gn/runtime_deps.cc',
+        'src/gn/rust_substitution_type.cc',
+        'src/gn/rust_tool.cc',
+        'src/gn/rust_values.cc',
+        'src/gn/rust_values_generator.cc',
+        'src/gn/rust_variables.cc',
+        'src/gn/scheduler.cc',
+        'src/gn/scope.cc',
+        'src/gn/scope_per_file_provider.cc',
+        'src/gn/settings.cc',
+        'src/gn/setup.cc',
+        'src/gn/source_dir.cc',
+        'src/gn/source_file.cc',
+        'src/gn/standard_out.cc',
+        'src/gn/string_atom.cc',
+        'src/gn/string_output_buffer.cc',
+        'src/gn/string_utils.cc',
+        'src/gn/substitution_list.cc',
+        'src/gn/substitution_pattern.cc',
+        'src/gn/substitution_type.cc',
+        'src/gn/substitution_writer.cc',
+        'src/gn/swift_values.cc',
+        'src/gn/swift_values_generator.cc',
+        'src/gn/swift_variables.cc',
+        'src/gn/switches.cc',
+        'src/gn/target.cc',
+        'src/gn/target_generator.cc',
+        'src/gn/template.cc',
+        'src/gn/token.cc',
+        'src/gn/tokenizer.cc',
+        'src/gn/tool.cc',
+        'src/gn/toolchain.cc',
+        'src/gn/trace.cc',
+        'src/gn/value.cc',
+        'src/gn/value_extractors.cc',
+        'src/gn/variables.cc',
+        'src/gn/version.cc',
+        'src/gn/visibility.cc',
+        'src/gn/visual_studio_utils.cc',
+        'src/gn/visual_studio_writer.cc',
+        'src/gn/xcode_object.cc',
+        'src/gn/xcode_writer.cc',
+        'src/gn/xml_element_writer.cc',
+        'src/util/exe_path.cc',
+        'src/util/msg_loop.cc',
+        'src/util/semaphore.cc',
+        'src/util/sys_info.cc',
+        'src/util/ticks.cc',
+        'src/util/worker_pool.cc',
+      ]},
+  }
+
+  executables = {
+      'gn': {'sources': [ 'src/gn/gn_main.cc' ], 'libs': []},
+
+      'gn_unittests': { 'sources': [
+        'src/gn/action_target_generator_unittest.cc',
+        'src/gn/analyzer_unittest.cc',
+        'src/gn/args_unittest.cc',
+        'src/gn/builder_unittest.cc',
+        'src/gn/c_include_iterator_unittest.cc',
+        'src/gn/command_format_unittest.cc',
+        'src/gn/commands_unittest.cc',
+        'src/gn/compile_commands_writer_unittest.cc',
+        'src/gn/config_unittest.cc',
+        'src/gn/config_values_extractors_unittest.cc',
+        'src/gn/escape_unittest.cc',
+        'src/gn/exec_process_unittest.cc',
+        'src/gn/filesystem_utils_unittest.cc',
+        'src/gn/file_writer_unittest.cc',
+        'src/gn/frameworks_utils_unittest.cc',
+        'src/gn/function_filter_unittest.cc',
+        'src/gn/function_foreach_unittest.cc',
+        'src/gn/function_forward_variables_from_unittest.cc',
+        'src/gn/function_get_label_info_unittest.cc',
+        'src/gn/function_get_path_info_unittest.cc',
+        'src/gn/function_get_target_outputs_unittest.cc',
+        'src/gn/function_process_file_template_unittest.cc',
+        'src/gn/function_rebase_path_unittest.cc',
+        'src/gn/function_template_unittest.cc',
+        'src/gn/function_toolchain_unittest.cc',
+        'src/gn/function_write_file_unittest.cc',
+        'src/gn/functions_target_rust_unittest.cc',
+        'src/gn/functions_target_unittest.cc',
+        'src/gn/functions_unittest.cc',
+        'src/gn/hash_table_base_unittest.cc',
+        'src/gn/header_checker_unittest.cc',
+        'src/gn/inherited_libraries_unittest.cc',
+        'src/gn/input_conversion_unittest.cc',
+        'src/gn/json_project_writer_unittest.cc',
+        'src/gn/rust_project_writer_unittest.cc',
+        'src/gn/rust_project_writer_helpers_unittest.cc',
+        'src/gn/label_pattern_unittest.cc',
+        'src/gn/label_unittest.cc',
+        'src/gn/loader_unittest.cc',
+        'src/gn/metadata_unittest.cc',
+        'src/gn/metadata_walk_unittest.cc',
+        'src/gn/ninja_action_target_writer_unittest.cc',
+        'src/gn/ninja_binary_target_writer_unittest.cc',
+        'src/gn/ninja_build_writer_unittest.cc',
+        'src/gn/ninja_bundle_data_target_writer_unittest.cc',
+        'src/gn/ninja_c_binary_target_writer_unittest.cc',
+        'src/gn/ninja_copy_target_writer_unittest.cc',
+        'src/gn/ninja_create_bundle_target_writer_unittest.cc',
+        'src/gn/ninja_generated_file_target_writer_unittest.cc',
+        'src/gn/ninja_group_target_writer_unittest.cc',
+        'src/gn/ninja_rust_binary_target_writer_unittest.cc',
+        'src/gn/ninja_target_command_util_unittest.cc',
+        'src/gn/ninja_target_writer_unittest.cc',
+        'src/gn/ninja_toolchain_writer_unittest.cc',
+        'src/gn/operators_unittest.cc',
+        'src/gn/output_conversion_unittest.cc',
+        'src/gn/parse_tree_unittest.cc',
+        'src/gn/parser_unittest.cc',
+        'src/gn/path_output_unittest.cc',
+        'src/gn/pattern_unittest.cc',
+        'src/gn/runtime_deps_unittest.cc',
+        'src/gn/scope_per_file_provider_unittest.cc',
+        'src/gn/scope_unittest.cc',
+        'src/gn/setup_unittest.cc',
+        'src/gn/source_dir_unittest.cc',
+        'src/gn/source_file_unittest.cc',
+        'src/gn/string_atom_unittest.cc',
+        'src/gn/string_output_buffer_unittest.cc',
+        'src/gn/string_utils_unittest.cc',
+        'src/gn/substitution_pattern_unittest.cc',
+        'src/gn/substitution_writer_unittest.cc',
+        'src/gn/target_unittest.cc',
+        'src/gn/template_unittest.cc',
+        'src/gn/test_with_scheduler.cc',
+        'src/gn/test_with_scope.cc',
+        'src/gn/tokenizer_unittest.cc',
+        'src/gn/unique_vector_unittest.cc',
+        'src/gn/value_unittest.cc',
+        'src/gn/vector_utils_unittest.cc',
+        'src/gn/version_unittest.cc',
+        'src/gn/visibility_unittest.cc',
+        'src/gn/visual_studio_utils_unittest.cc',
+        'src/gn/visual_studio_writer_unittest.cc',
+        'src/gn/xcode_object_unittest.cc',
+        'src/gn/xml_element_writer_unittest.cc',
+        'src/util/test/gn_test.cc',
+      ], 'libs': []},
+  }
+
+  if platform.is_posix():
+    static_libraries['base']['sources'].extend([
+        'src/base/files/file_enumerator_posix.cc',
+        'src/base/files/file_posix.cc',
+        'src/base/files/file_util_posix.cc',
+        'src/base/posix/file_descriptor_shuffle.cc',
+        'src/base/posix/safe_strerror.cc',
+    ])
+
+  if platform.is_windows():
+    static_libraries['base']['sources'].extend([
+        'src/base/files/file_enumerator_win.cc',
+        'src/base/files/file_util_win.cc',
+        'src/base/files/file_win.cc',
+        'src/base/win/registry.cc',
+        'src/base/win/scoped_handle.cc',
+        'src/base/win/scoped_process_information.cc',
+    ])
+
+    if platform.is_msvc():
+      libs.extend([
+          'advapi32.lib',
+          'dbghelp.lib',
+          'kernel32.lib',
+          'ole32.lib',
+          'shell32.lib',
+          'user32.lib',
+          'userenv.lib',
+          'version.lib',
+          'winmm.lib',
+          'ws2_32.lib',
+          'Shlwapi.lib',
+      ])
+    else:
+      libs.extend([
+          '-ladvapi32',
+          '-ldbghelp',
+          '-lkernel32',
+          '-lole32',
+          '-lshell32',
+          '-luser32',
+          '-luserenv',
+          '-lversion',
+          '-lwinmm',
+          '-lws2_32',
+          '-lshlwapi',
+      ])
+
+
+  libs.extend(options.link_libs)
+
+  # we just build static libraries that GN needs
+  executables['gn']['libs'].extend(static_libraries.keys())
+  executables['gn_unittests']['libs'].extend(static_libraries.keys())
+
+  WriteGenericNinja(path, static_libraries, executables, cxx, ar, ld,
+                    platform, host, options, cflags, ldflags,
+                    libflags, include_dirs, libs)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/docs/cross_compiles.md b/docs/cross_compiles.md
new file mode 100644 (file)
index 0000000..ad71b94
--- /dev/null
@@ -0,0 +1,126 @@
+# How GN handles cross-compiling
+
+## As a GN user
+
+GN has robust support for doing cross compiles and building things for
+multiple architectures in a single build (e.g., to build some things to
+run locally and some things to run on an embedded device). In fact,
+there is no limit on the number of different architectures you can build
+at once; the Chromium build uses at least four in some configurations.
+
+To start, GN has the concepts of a _host_ and a _target_. The host is
+the platform that the build is run on, and the target is the platform
+where the code will actually run (This is different from
+[autotools](http://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html)'
+terminology, but uses the more common terminology for cross
+compiling).
+
+(Confusingly, GN also refers to each build artifact -- an executable,
+library, etc. -- as a target. On this page, we will use "target" only to
+refer to the system you want to run your code on, and use "rule" or some
+other synonym to refer to a specific build artifact).
+
+When GN starts up, the `host_os` and `host_cpu` variables are set
+automatically to match the operating system (they can be overridden in
+args files, which can be useful in weird corner cases). The user can
+specify that they want to do a cross-compile by setting either or both
+of `target_os` and `target_cpu`; if they are not set, the build config
+files will usually set them to the host's values, though the Chromium
+build will set target\_cpu to "arm" if target\_os is set to "android").
+
+So, for example, running on an x64 Linux machine:
+
+```
+gn gen out/Default
+```
+
+is equivalent to:
+
+```
+gn gen out/Default --args='target_os="linux" target_cpu="x64"'
+```
+
+To do an 32-bit ARM Android cross-compile, do:
+
+```
+gn gen out/Default --args='target_os="android"'
+```
+
+(We don't have to specify target\_cpu because of the conditionals
+mentioned above).
+
+And, to do a 64-bit MIPS Chrome OS cross-compile:
+
+```
+gn gen out/Default --args='target_os="chromeos" target_cpu="mips64el"'
+```
+
+## As a BUILD.gn author
+
+If you are editing build files outside of the //build directory (i.e.,
+not directly working on toolchains, compiler configs, etc.), generally
+you only need to worry about a few things:
+
+The `current_toolchain`, `current_cpu`, and `current_os` variables
+reflect the settings that are **currently** in effect in a given rule.
+The `is_linux`, `is_win` etc. variables are updated to reflect the
+current settings, and changes to `cflags`, `ldflags` and so forth also
+only apply to the current toolchain and the current thing being built.
+
+You can also refer to the `target_cpu` and `target_os` variables. This
+is useful if you need to do something different on the host depending on
+which target\_arch is requested; the values are constant across all
+toolchains. You can do similar things for the `host_cpu` and `host_os`
+variables, but should generally never need to.
+
+For the default toolchain, `target_cpu` and `current_cpu` are the same. For a
+secondary toolchain, `current_cpu` is set based on the toolchain definition
+and `target_cpu` remains the same. When writing rules, **`current_cpu` should
+be used rather than `target_cpu` most of the time**.
+
+By default, dependencies listed in the `deps` variable of a rule use the
+same (currently active) toolchain. You may specify a different toolchain
+using the `foo(bar)` label notation as described in [the label section
+of the reference doc](reference.md#Toolchains).
+
+Here's an example of when to use `target_cpu` vs `current_cpu`:
+
+```
+declare_args() {
+  # Applies only to toolchains targeting target_cpu.
+  sysroot = ""
+}
+
+config("my_config") {
+  # Uses current_cpu because compile flags are toolchain-dependent.
+  if (current_cpu == "arm") {
+    defines = [ "CPU_IS_32_BIT" ]
+  } else {
+    defines = [ "CPU_IS_64_BIT" ]
+  }
+  # Compares current_cpu with target_cpu to see whether current_toolchain
+  # has the same architecture as target_toolchain.
+  if (sysroot != "" && current_cpu == target_cpu) {
+    cflags = [
+      "-isysroot",
+      sysroot,
+    ]
+  }
+}
+```
+
+## As a //build/config or //build/toolchain author
+
+The `default_toolchain` is declared in the `BUILDCONFIG.gn` file (in Google
+projects this normally is in the `//build/config` directory). Usually the
+`default_toolchain` should be the toolchain for the `target_os` and
+`target_cpu`. The `current_toolchain` reflects the toolchain that is currently
+in effect for a rule.
+
+Be sure you understand the differences between `host_cpu`, `target_cpu`,
+`current_cpu`, and `toolchain_cpu` (and the os equivalents). The first
+two are set as described above. You are responsible for making sure that
+`current_cpu` is set appropriately in your toolchain definitions; if you
+are using the stock templates like `gcc_toolchain` and `msvc_toolchain`,
+that means you are responsible for making sure that `toolchain_cpu` and
+`toolchain_os` are set as appropriate in the template invocations.
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644 (file)
index 0000000..699df3d
--- /dev/null
@@ -0,0 +1,58 @@
+# GN Frequently Asked Questions
+
+[TOC]
+
+## Where is the GN documentation?
+
+GN has extensive built-in help, so you can run `gn help`, but you can
+also see all of the help on [the reference page](reference.md). See
+also the [quick start](quick_start.md) guide and the [language and
+operation details](language.md).
+
+## Can I generate XCode or Visual Studio projects?
+
+You can generate skeleton (or wrapper) projects for Xcode, Visual Studio,
+QTCreator, and Eclipse that will list the files and targets in the
+build, but use Ninja to do the actual build. You cannot generate "real"
+projects that look like native ones like GYP could.
+
+Run `gn help gen` for more details.
+
+## How do I generate common build variants?
+
+In GN, args go with a build directory rather than being global in the
+environment. To edit the args for your `out/Default` build directory:
+
+```
+gn args out/Default
+```
+
+You can set variables in that file:
+
+  * The default is a debug build. To do a release build add
+    `is_debug = false`
+  * The default is a static build. To do a component build add
+    `is_component_build = true`
+  * The default is a developer build. To do an official build, set
+    `is_official_build = true`
+  * The default is Chromium branding. To do Chrome branding, set
+    `is_chrome_branded = true`
+
+## How do I do cross-compiles?
+
+GN has robust support for doing cross compiles and building things for
+multiple architectures in a single build.
+
+See [GNCrossCompiles](cross_compiles.md) for more info.
+
+## Can I control what targets are built by default?
+
+Yes! If you create a group target called "default" in the top-level (root)
+build file, i.e., "//:default", GN will tell Ninja to build that by
+default, rather than building everything.
+
+## Are there any public presentations on GN?
+
+[There's at least one](https://docs.google.com/presentation/d/15Zwb53JcncHfEwHpnG_PoIbbzQ3GQi_cpujYwbpcbZo/edit?usp=sharing), from 2015. There
+haven't been big changes since then apart from moving it to a standalone
+repo, so it should still be relevant.
diff --git a/docs/language.md b/docs/language.md
new file mode 100644 (file)
index 0000000..137300a
--- /dev/null
@@ -0,0 +1,535 @@
+# GN Language and Operation
+
+[TOC]
+
+## Introduction
+
+This page describes many of the language details and behaviors.
+
+### Use the built-in help!
+
+GN has an extensive built-in help system which provides a reference for
+every function and built-in variable. This page is more high-level.
+
+```
+gn help
+```
+
+You can also see the
+[slides](https://docs.google.com/presentation/d/15Zwb53JcncHfEwHpnG_PoIbbzQ3GQi_cpujYwbpcbZo/edit?usp=sharing)
+from a March, 2016 introduction to GN. The speaker notes contain the full
+content.
+
+### Design philosophy
+
+  * Writing build files should not be a creative endeavour. Ideally two
+    people should produce the same buildfile given the same
+    requirements. There should be no flexibility unless it's absolutely
+    needed. As many things should be fatal errors as possible.
+
+  * The definition should read more like code than rules. I don't want
+    to write or debug Prolog. But everybody on our team can write and
+    debug C++ and Python.
+
+  * The build language should be opinionated as to how the build should
+    work. It should not necessarily be easy or even possible to express
+    arbitrary things. We should be changing source and tooling to make
+    the build simpler rather than making everything more complicated to
+    conform to external requirements (within reason).
+
+  * Be like Blaze when it makes sense (see "Differences and similarities
+    to Blaze" below).
+
+## Language
+
+GN uses an extremely simple, dynamically typed language. The types are:
+
+  * Boolean (`true`, `false`).
+  * 64-bit signed integers.
+  * Strings.
+  * Lists (of any other types).
+  * Scopes (sort of like a dictionary, only for built-in stuff).
+
+There are some built-in variables whose values depend on the current
+environment. See `gn help` for more.
+
+There are purposefully many omissions in the language. There are no
+user-defined function calls, for example (templates are the closest thing). As
+per the above design philosophy, if you need this kind of thing you're probably
+doing it wrong.
+
+The full grammar for language nerds is available in `gn help grammar`.
+
+### Strings
+
+Strings are enclosed in double-quotes and use backslash as the escape
+character. The only escape sequences supported are:
+
+  * `\"` (for literal quote)
+  * `\$` (for literal dollars sign)
+  * `\\` (for literal backslash)
+
+Any other use of a backslash is treated as a literal backslash. So, for
+example, `\b` used in patterns does not need to be escaped, nor do most Windows
+paths like `"C:\foo\bar.h"`.
+
+Simple variable substitution is supported via `$`, where the word
+following the dollars sign is replaced with the value of the variable.
+You can optionally surround the name with `{}` if there is not a
+non-variable-name character to terminate the variable name. More complex
+expressions are not supported, only variable name substitution.
+
+```
+a = "mypath"
+b = "$a/foo.cc"  # b -> "mypath/foo.cc"
+c = "foo${a}bar.cc"  # c -> "foomypathbar.cc"
+```
+
+You can encode 8-bit characters using "$0xFF" syntax, so a string with newlines
+(hex 0A) would `"look$0x0Alike$0x0Athis"`.
+
+### Lists
+
+Aside from telling empty lists from non empty lists (`a == []`), there is no
+way to get the length of a list. If you find yourself wanting to do this kind
+of thing, you're trying to do too much work in the build.
+
+Lists support appending:
+
+```
+a = [ "first" ]
+a += [ "second" ]  # [ "first", "second" ]
+a += [ "third", "fourth" ]  # [ "first", "second", "third", "fourth" ]
+b = a + [ "fifth" ]  # [ "first", "second", "third", "fourth", "fifth" ]
+```
+
+Appending a list to another list appends the items in the second list
+rather than appending the list as a nested member.
+
+You can remove items from a list:
+
+```
+a = [ "first", "second", "third", "first" ]
+b = a - [ "first" ]  # [ "second", "third" ]
+a -= [ "second" ]  # [ "first", "third", "first" ]
+```
+
+The - operator on a list searches for matches and removes all matching
+items. Subtracting a list from another list will remove each item in the
+second list.
+
+If no matching items are found, an error will be thrown, so you need to
+know in advance that the item is there before removing it. Given that
+there is no way to test for inclusion, the main use-case is to set up a
+master list of files or flags, and to remove ones that don't apply to
+the current build based on various conditions.
+
+Stylistically, prefer to only add to lists and have each source file or
+dependency appear once. This is the opposite of the advice Chrome-team used to
+give for GYP (GYP would prefer to list all files, and then remove the ones you
+didn't want in conditionals).
+
+Lists support zero-based subscripting to extract values:
+
+```
+a = [ "first", "second", "third" ]
+b = a[1]  # -> "second"
+```
+
+The \[\] operator is read-only and can not be used to mutate the
+list. The primary use-case of this is when an external script returns
+several known values and you want to extract them.
+
+There are some cases where it's easy to overwrite a list when you mean
+to append to it instead. To help catch this case, it is an error to
+assign a nonempty list to a variable containing an existing nonempty
+list. If you want to get around this restriction, first assign the
+destination variable to the empty list.
+
+```
+a = [ "one" ]
+a = [ "two" ]  # Error: overwriting nonempty list with a nonempty list.
+a = []         # OK
+a = [ "two" ]  # OK
+```
+
+Note that execution of the build script is done without intrinsic
+knowledge of the meaning of the underlying data. This means that it
+doesn't know that `sources` is a list of file names, for example. So if
+you remove an item, it must match the literal string rather than
+specifying a different name that will resolve to the same file name.
+
+### Conditionals
+
+Conditionals look like C:
+
+```
+  if (is_linux || (is_win && target_cpu == "x86")) {
+    sources -= [ "something.cc" ]
+  } else if (...) {
+    ...
+  } else {
+    ...
+  }
+```
+
+You can use them in most places, even around entire targets if the
+target should only be declared in certain circumstances.
+
+### Looping
+
+You can iterate over a list with `foreach`. This is discouraged. Most things
+the build should do can normally be expressed without doing this, and if you
+find it necessary it may be an indication you're doing too much work in the
+metabuild.
+
+```
+foreach(i, mylist) {
+  print(i)  # Note: i is a copy of each element, not a reference to it.
+}
+```
+
+### Function calls
+
+Simple function calls look like most other languages:
+
+```
+print("hello, world")
+assert(is_win, "This should only be executed on Windows")
+```
+
+Such functions are built-in and the user can not define new ones.
+
+Some functions take a block of code enclosed by `{ }` following them:
+
+```
+static_library("mylibrary") {
+  sources = [ "a.cc" ]
+}
+```
+
+Most of these define targets. The user can define new functions like this
+with the template mechanism discussed below.
+
+Precisely, this expression means that the block becomes an argument to the
+function for the function to execute. Most of the block-style functions execute
+the block and treat the resulting scope as a dictionary of variables to read.
+
+### Scoping and execution
+
+Files and function calls followed by `{ }` blocks introduce new scopes. Scopes
+are nested. When you read a variable, the containing scopes will be searched in
+reverse order until a matching name is found. Variable writes always go to the
+innermost scope.
+
+There is no way to modify any enclosing scope other than the innermost
+one. This means that when you define a target, for example, nothing you
+do inside of the block will "leak out" into the rest of the file.
+
+`if`/`else`/`foreach` statements, even though they use `{ }`, do not introduce
+a new scope so changes will persist outside of the statement.
+
+## Naming things
+
+### File and directory names
+
+File and directory names are strings and are interpreted as relative to
+the current build file's directory. There are three possible forms:
+
+Relative names:
+
+```
+"foo.cc"
+"src/foo.cc"
+"../src/foo.cc"
+```
+
+Source-tree absolute names:
+
+```
+"//net/foo.cc"
+"//base/test/foo.cc"
+```
+
+System absolute names (rare, normally used for include directories):
+
+```
+"/usr/local/include/"
+"/C:/Program Files/Windows Kits/Include"
+```
+
+## Build configuration
+
+## Targets
+
+A target is a node in the build graph. It usually represents some kind
+of executable or library file that will be generated. Targets depend on
+other targets. The built-in target types (see `gn help <targettype>` for
+more help) are:
+
+  * `action`: Run a script to generate a file.
+  * `action_foreach`: Run a script once for each source file.
+  * `bundle_data`: Declare data to go into a Mac/iOS bundle.
+  * `create_bundle`: Creates a Mac/iOS bundle.
+  * `executable`: Generates an executable file.
+  * `group`: A virtual dependency node that refers to one or more other
+    targets.
+  * `shared_library`: A .dll or .so.
+  * `loadable_module`: A .dll or .so loadable only at runtime.
+  * `source_set`: A lightweight virtual static library (usually
+    preferrable over a real static library since it will build faster).
+  * `static_library`: A .lib or .a file (normally you'll want a
+    `source_set` instead).
+
+You can extend this to make custom target types using templates (see below). In
+Chrome some of the more commonly-used templates are:
+
+  * `component`: Either a source set or shared library, depending on the
+    build type.
+  * `test`: A test executable. On mobile this will create the appropriate
+    native app type for tests.
+  * `app`: Executable or Mac/iOS application.
+  * `android_apk`: Make an APK. There are a _lot_ of other Android ones, see
+    `//build/config/android/rules.gni`.
+
+## Configs
+
+Configs are named objects that specify sets of flags, include
+directories, and defines. They can be applied to a target and pushed to
+dependent targets.
+
+To define a config:
+
+```
+config("myconfig") {
+  includes = [ "src/include" ]
+  defines = [ "ENABLE_DOOM_MELON" ]
+}
+```
+
+To apply a config to a target:
+
+```
+executable("doom_melon") {
+  configs = [ ":myconfig" ]
+}
+```
+
+It is common for the build config file to specify target defaults that
+set a default list of configs. Targets can add or remove to this list as
+needed. So in practice you would usually use `configs += ":myconfig"` to
+append to the list of defaults.
+
+See `gn help config` for more information about how configs are declared
+and applied.
+
+### Public configs
+
+A target can apply settings to other targets that depend on it. The most
+common example is a third party target that requires some defines or
+include directories for its headers to compile properly. You want these
+settings to apply both to the compile of the third party library itself,
+as well as all targets that use the library.
+
+To do this, you write a config with the settings you want to apply:
+
+```
+config("my_external_library_config") {
+  includes = "."
+  defines = [ "DISABLE_JANK" ]
+}
+```
+
+Then this config is added to the target as a "public" config. It will
+apply both to the target as well as targets that directly depend on it.
+
+```
+shared_library("my_external_library") {
+  ...
+  # Targets that depend on this get this config applied.
+  public_configs = [ ":my_external_library_config" ]
+}
+```
+
+Dependent targets can in turn forward this up the dependency tree
+another level by adding your target as a "public" dependency.
+
+```
+static_library("intermediate_library") {
+  ...
+  # Targets that depend on this one also get the configs from "my external library".
+  public_deps = [ ":my_external_library" ]
+}
+```
+
+A target can forward a config to all dependents until a link boundary is
+reached by setting it as an `all_dependent_config`. This is strongly
+discouraged as it can spray flags and defines over more of the build than
+necessary. Instead, use public_deps to control which flags apply where.
+
+In Chrome, prefer the build flag header system (`build/buildflag_header.gni`)
+for defines which prevents most screw-ups with compiler defines.
+
+## Templates
+
+Templates are GN's primary way to re-use code. Typically, a template
+would expand to one or more other target types.
+
+```
+# Declares a script that compiles IDL files to source, and then compiles those
+# source files.
+template("idl") {
+  # Always base helper targets on target_name so they're unique. Target name
+  # will be the string passed as the name when the template is invoked.
+  idl_target_name = "${target_name}_generate"
+  action_foreach(idl_target_name) {
+    ...
+  }
+
+  # Your template should always define a target with the name target_name.
+  # When other targets depend on your template invocation, this will be the
+  # destination of that dependency.
+  source_set(target_name) {
+    ...
+    deps = [ ":$idl_target_name" ]  # Require the sources to be compiled.
+  }
+}
+```
+
+Typically your template definition would go in a `.gni` file and users
+would import that file to see the template definition:
+
+```
+import("//tools/idl_compiler.gni")
+
+idl("my_interfaces") {
+  sources = [ "a.idl", "b.idl" ]
+}
+```
+
+Declaring a template creates a closure around the variables in scope at
+that time. When the template is invoked, the magic variable `invoker` is
+used to read variables out of the invoking scope. The template would
+generally copy the values its interested in into its own scope:
+
+```
+template("idl") {
+  source_set(target_name) {
+    sources = invoker.sources
+  }
+}
+```
+
+The current directory when a template executes will be that of the
+invoking build file rather than the template source file. This is so
+files passed in from the template invoker will be correct (this
+generally accounts for most file handling in a template). However, if
+the template has files itself (perhaps it generates an action that runs
+a script), you will want to use absolute paths ("//foo/...") to refer to
+these files to account for the fact that the current directory will be
+unpredictable during invocation. See `gn help template` for more
+information and more complete examples.
+
+## Other features
+
+### Imports
+
+You can import `.gni` files into the current scope with the `import`
+function. This is _not_ an include in the C++ sense. The imported file is
+executed independently and the resulting scope is copied into the current file
+(C++ executes the included file in the current context of when the
+include directive appeared). This allows the results of the import to be
+cached, and also prevents some of the more "creative" uses of includes like
+multiply-included files.
+
+Typically, a `.gni` would define build arguments and templates. See `gn
+help import` for more.
+
+Your `.gni` file can define temporary variables that are not exported files
+that include it by using a preceding underscore in the name like `_this`.
+
+### Path processing
+
+Often you will want to make a file name or a list of file names relative
+to a different directory. This is especially common when running
+scripts, which are executed with the build output directory as the
+current directory, while build files usually refer to files relative to
+their containing directory.
+
+You can use `rebase_path` to convert directories. See `gn help
+rebase_path` for more help and examples. Typical usage to convert a file
+name relative to the current directory to be relative to the root build
+directory would be: ``` new_paths = rebase_path("myfile.c",
+root_build_dir) ```
+
+### Patterns
+
+Patterns are used to generate the output file names for a given set of
+inputs for custom target types, and to automatically remove files from
+the list values (see `gn help filter_include` and `gn help filter_exclude`).
+
+They are like simple regular expressions. See `gn help label_pattern`
+for more.
+
+### Executing scripts
+
+There are two ways to execute scripts. All external scripts in GN are in
+Python. The first way is as a build step. Such a script would take some
+input and generate some output as part of the build. Targets that invoke
+scripts are declared with the "action" target type (see `gn help
+action`).
+
+The second way to execute scripts is synchronously during build file
+execution. This is necessary in some cases to determine the set of files
+to compile, or to get certain system configurations that the build file
+might depend on. The build file can read the stdout of the script and
+act on it in different ways.
+
+Synchronous script execution is done by the `exec_script` function (see
+`gn help exec_script` for details and examples). Because synchronously
+executing a script requires that the current buildfile execution be
+suspended until a Python process completes execution, relying on
+external scripts is slow and should be minimized.
+
+To prevent abuse, files permitted to call `exec_script` can be whitelisted in
+the toplevel `.gn` file. Chrome does this to require additional code review
+for such additions. See `gn help dotfile`.
+
+You can synchronously read and write files which is discouraged but
+occasionally necessary when synchronously running scripts. The typical use-case
+would be to pass a list of file names longer than the command-line limits of
+the current platform. See `gn help read_file` and `gn help write_file` for how
+to read and write files. These functions should be avoided if at all possible.
+
+Actions that exceed command-line length limits can use response files to
+get around this limitation without synchronously writing files. See
+`gn help response_file_contents`.
+
+# Differences and similarities to Blaze
+
+Blaze is Google's internal build system, now publicly released as
+[Bazel](http://bazel.io/). It has inspired a number of other systems such as
+[Pants](http://www.pantsbuild.org/) and [Buck](http://facebook.github.io/buck/).
+
+In Google's homogeneous environment, the need for conditionals is very
+low and they can get by with a few hacks (`abi_deps`). Chrome uses
+conditionals all over the place and the need to add these is the main
+reason for the files looking different.
+
+GN also adds the concept of "configs" to manage some of the trickier
+dependency and configuration problems which likewise don't arise on the
+server. Blaze has a concept of a "configuration" which is like a GN
+toolchain, but built into the tool itself. The way that toolchains work
+in GN is a result of trying to separate this concept out into the build
+files in a clean way.
+
+GN keeps some GYP concept like "all dependent" settings which work a bit
+differently in Blaze. This is partially to make conversion from the existing
+GYP code easier, and the GYP constructs generally offer more fine-grained
+control (which is either good or bad, depending on the situation).
+
+GN also uses GYP names like "sources" instead of "srcs" since
+abbreviating this seems needlessly obscure, although it uses Blaze's
+"deps" since "dependencies" is so hard to type. Chromium also compiles
+multiple languages in one target so specifying the language type on the
+target name prefix was dropped (e.g. from `cc_library`).
diff --git a/docs/quick_start.md b/docs/quick_start.md
new file mode 100644 (file)
index 0000000..fc99e86
--- /dev/null
@@ -0,0 +1,344 @@
+# GN Quick Start guide
+
+[TOC]
+
+## Running GN
+
+You just run `gn` from the command line. For large projects, GN is versioned
+and distributed with the source checkout.
+
+  * For Chromium and Chromium-based projects, there is a script in
+    `depot_tools`, which is presumably in your PATH, with this name. The script
+    will find the binary in the source tree containing the current directory and
+    run it.
+
+  * For Fuchsia in-tree development, run `fx gn ...` which will find the right
+    GN binary and run it with the given arguments.
+
+  * For other projects, see your project's documentation.
+
+## Setting up a build
+
+Unlike some other build systems, with GN you set up your own build directories
+with the settings you want. This lets you maintain as many different builds in
+parallel as you need.
+
+Once you set up a build directory, the Ninja files will be automatically
+regenerated if they're out of date when you build in that directory so you
+should not have to re-run GN.
+
+To make a build directory:
+
+```
+gn gen out/my_build
+```
+
+## Passing build arguments
+
+Set build arguments on your build directory by running:
+
+```
+gn args out/my_build
+```
+
+This will bring up an editor. Type build args into that file like this:
+
+```
+is_component_build = true
+is_debug = false
+```
+
+The available variables will depend on your build (this example is from
+Chromium). You can see the list of available arguments and their default values
+by typing
+
+```
+gn args --list out/my_build
+```
+
+on the command line. Note that you have to specify the build directory
+for this command because the available arguments can change according
+to the build.
+
+Chrome developers can also read the [Chrome-specific build
+configuration](http://www.chromium.org/developers/gn-build-configuration)
+instructions for more information.
+
+## Cross-compiling to a target OS or architecture
+
+Run `gn args out/Default` (substituting your build directory as needed) and
+add one or more of the following lines for common cross-compiling options.
+
+```
+target_os = "chromeos"
+target_os = "android"
+
+target_cpu = "arm"
+target_cpu = "x86"
+target_cpu = "x64"
+```
+
+See [GN cross compiles](cross_compiles.md) for more info.
+
+## Step-by-step
+
+### Adding a build file
+
+Go to the directory `examples/simple_build`. This is the root of a minimal GN
+repository.
+
+In that directory there is a `tutorial` directory. There is already a
+`tutorial.cc` file that's not hooked up to the build. Create a new `BUILD.gn`
+file in that directory for our new target.
+
+```
+executable("tutorial") {
+  sources = [
+    "tutorial.cc",
+  ]
+}
+```
+
+Now we just need to tell the build about this new target. Open the `BUILD.gn`
+file in the parent (`simple_build`) directory. GN starts by loading this root
+file, and then loads all dependencies ourward from here, so we just need to add
+a reference to our new target from this file.
+
+You could add our new target as a dependency from one of the existing targets in
+the `simple_build/BUILD.gn` file, but it usually doesn't make a lot of sense to
+have an executable as a depdency of another executable (they can't be linked).
+So let's make a "tools" group. In GN, a "group" is just a collection of
+dependencies that's not complied or linked:
+
+```
+group("tools") {
+  deps = [
+    # This will expand to the name "//tutorial:tutorial" which is the full name
+    # of our new target. Run "gn help labels" for more.
+    "//tutorial",
+  ]
+}
+```
+
+### Testing your addition
+
+From the command line in the `simple_build` directory:
+
+```
+gn gen out
+ninja -C out tutorial
+out/tutorial
+```
+
+You should see "Hello, world." output to the console.
+
+Side note: GN encourages target names for static libraries that aren't globally
+unique. To build one of these, you can pass the label with its path (but no leading
+"//") to ninja:
+
+```
+ninja -C out some/path/to/target:my_target
+```
+
+### Declaring dependencies
+
+Let's look at the targets defined in
+[examples/simple_build/BUILD.gn](../examples/simple_build/BUILD.gn). There is a
+static library that defines one function, `GetStaticText()`:
+
+```
+static_library("hello_static") {
+  sources = [
+    "hello_static.cc",
+    "hello_static.h",
+  ]
+}
+```
+
+There is also a shared library that defines one function `GetSharedText()`:
+
+```
+shared_library("hello_shared") {
+  sources = [
+    "hello_shared.cc",
+    "hello_shared.h",
+  ]
+
+  defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
+}
+```
+
+This also illustrates how to set preprocessor defines for a target. To set more
+than one or to assign values, use this form:
+
+```
+defines = [
+  "HELLO_SHARED_IMPLEMENTATION",
+  "ENABLE_DOOM_MELON=0",
+]
+```
+
+Now let's look at the executable that depends on these two libraries:
+
+```
+executable("hello") {
+  sources = [
+    "hello.cc",
+  ]
+
+  deps = [
+    ":hello_shared",
+    ":hello_static",
+  ]
+}
+```
+
+This executable includes one source file and depends on the previous
+two libraries. Labels starting with a colon refer to a target with that name in
+the current BUILD.gn file.
+
+### Test the binary
+
+From the command line in the `simple_build` directory:
+
+```
+ninja -C out hello
+out/hello
+```
+
+Note that you **didn't** need to re-run GN. GN will automatically rebuild
+the ninja files when any build file has changed. You know this happens
+when ninja prints `[1/1] Regenerating ninja files` at the beginning of
+execution.
+
+### Putting settings in a config
+
+Users of a library often need compiler flags, defines, and include directories
+applied to them. To do this, put all such settings into a "config" which is a
+named collection of settings (but not sources or dependencies):
+
+```
+config("my_lib_config") {
+  defines = [ "ENABLE_DOOM_MELON" ]
+  include_dirs = [ "//third_party/something" ]
+}
+```
+
+To apply a config's settings to a target, add it to the `configs` list:
+
+```
+static_library("hello_shared") {
+  ...
+  # Note "+=" here is usually required, see "default configs" below.
+  configs += [
+    ":my_lib_config",
+  ]
+}
+```
+
+A config can be applied to all targets that depend on the current one by putting
+its label in the `public_configs` list:
+
+```
+static_library("hello_shared") {
+  ...
+  public_configs = [
+    ":my_lib_config",
+  ]
+}
+```
+
+The `public_configs` also applies to the current target, so there's no need to
+list a config in both places.
+
+### Default configs
+
+The build configuration will set up some settings that apply to every target by
+default. These will normally be set as a default list of configs. You can see
+this using the "print" command which is useful for debugging:
+
+```
+executable("hello") {
+  print(configs)
+}
+```
+
+Running GN will print something like:
+
+```
+$ gn gen out
+["//build:compiler_defaults", "//build:executable_ldconfig"]
+Done. Made 5 targets from 5 files in 9ms
+```
+
+Targets can modify this list to change their defaults. For example, the build
+setup might turn off exceptions by default by adding a `no_exceptions` config,
+but a target might re-enable them by replacing it with a different one:
+
+```
+executable("hello") {
+  ...
+  configs -= [ "//build:no_exceptions" ]  # Remove global default.
+  configs += [ "//build:exceptions" ]  # Replace with a different one.
+}
+```
+
+Our print command from above could also be expressed using string interpolation.
+This is a way to convert values to strings. It uses the symbol "$" to refer to a
+variable:
+
+```
+print("The configs for the target $target_name are $configs")
+```
+
+## Add a new build argument
+
+You declare which arguments you accept and specify default values via
+`declare_args`.
+
+```
+declare_args() {
+  enable_teleporter = true
+  enable_doom_melon = false
+}
+```
+
+See `gn help buildargs` for an overview of how this works.
+See `gn help declare_args` for specifics on declaring them.
+
+It is an error to declare a given argument more than once in a given scope, so
+care should be used in scoping and naming arguments.
+
+## Don't know what's going on?
+
+You can run GN in verbose mode to see lots of messages about what it's
+doing. Use `-v` for this.
+
+### The "desc" command
+
+You can run `gn desc <build_dir> <targetname>` to get information about
+a given target:
+
+```
+gn desc out/Default //foo/bar:say_hello
+```
+
+will print out lots of exciting information. You can also print just one
+section. Lets say you wanted to know where your `TWO_PEOPLE` define
+came from on the `say_hello` target:
+
+```
+> gn desc out/Default //foo/bar:say_hello defines --blame
+...lots of other stuff omitted...
+  From //foo/bar:hello_config
+       (Added by //foo/bar/BUILD.gn:12)
+    TWO_PEOPLE
+```
+
+Another particularly interesting variation:
+
+```
+gn desc out/Default //base:base_i18n deps --tree
+```
+
+See `gn help desc` for more.
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644 (file)
index 0000000..33ee391
--- /dev/null
@@ -0,0 +1,7811 @@
+# GN Reference
+
+*This page is automatically generated from* `gn help --markdown all`.
+
+## Contents
+
+*   [Commands](#commands)
+    *   [analyze: Analyze which targets are affected by a list of files.](#cmd_analyze)
+    *   [args: Display or configure arguments declared by the build.](#cmd_args)
+    *   [check: Check header dependencies.](#cmd_check)
+    *   [clean: Cleans the output directory.](#cmd_clean)
+    *   [clean_stale: Cleans the stale output files from the output directory.](#cmd_clean_stale)
+    *   [desc: Show lots of insightful information about a target or config.](#cmd_desc)
+    *   [format: Format .gn files.](#cmd_format)
+    *   [gen: Generate ninja files.](#cmd_gen)
+    *   [help: Does what you think.](#cmd_help)
+    *   [ls: List matching targets.](#cmd_ls)
+    *   [meta: List target metadata collection results.](#cmd_meta)
+    *   [outputs: Which files a source/target make.](#cmd_outputs)
+    *   [path: Find paths between two targets.](#cmd_path)
+    *   [refs: Find stuff referencing a target or file.](#cmd_refs)
+*   [Target declarations](#targets)
+    *   [action: Declare a target that runs a script a single time.](#func_action)
+    *   [action_foreach: Declare a target that runs a script over a set of files.](#func_action_foreach)
+    *   [bundle_data: [iOS/macOS] Declare a target without output.](#func_bundle_data)
+    *   [copy: Declare a target that copies files.](#func_copy)
+    *   [create_bundle: [iOS/macOS] Build an iOS or macOS bundle.](#func_create_bundle)
+    *   [executable: Declare an executable target.](#func_executable)
+    *   [generated_file: Declare a generated_file target.](#func_generated_file)
+    *   [group: Declare a named group of targets.](#func_group)
+    *   [loadable_module: Declare a loadable module target.](#func_loadable_module)
+    *   [rust_library: Declare a Rust library target.](#func_rust_library)
+    *   [rust_proc_macro: Declare a Rust procedural macro target.](#func_rust_proc_macro)
+    *   [shared_library: Declare a shared library target.](#func_shared_library)
+    *   [source_set: Declare a source set target.](#func_source_set)
+    *   [static_library: Declare a static library target.](#func_static_library)
+    *   [target: Declare an target with the given programmatic type.](#func_target)
+*   [Buildfile functions](#functions)
+    *   [assert: Assert an expression is true at generation time.](#func_assert)
+    *   [config: Defines a configuration object.](#func_config)
+    *   [declare_args: Declare build arguments.](#func_declare_args)
+    *   [defined: Returns whether an identifier is defined.](#func_defined)
+    *   [exec_script: Synchronously run a script and return the output.](#func_exec_script)
+    *   [filter_exclude: Remove values that match a set of patterns.](#func_filter_exclude)
+    *   [filter_include: Remove values that do not match a set of patterns.](#func_filter_include)
+    *   [foreach: Iterate over a list.](#func_foreach)
+    *   [forward_variables_from: Copies variables from a different scope.](#func_forward_variables_from)
+    *   [get_label_info: Get an attribute from a target's label.](#func_get_label_info)
+    *   [get_path_info: Extract parts of a file or directory name.](#func_get_path_info)
+    *   [get_target_outputs: [file list] Get the list of outputs from a target.](#func_get_target_outputs)
+    *   [getenv: Get an environment variable.](#func_getenv)
+    *   [import: Import a file into the current scope.](#func_import)
+    *   [not_needed: Mark variables from scope as not needed.](#func_not_needed)
+    *   [pool: Defines a pool object.](#func_pool)
+    *   [print: Prints to the console.](#func_print)
+    *   [process_file_template: Do template expansion over a list of files.](#func_process_file_template)
+    *   [read_file: Read a file into a variable.](#func_read_file)
+    *   [rebase_path: Rebase a file or directory to another location.](#func_rebase_path)
+    *   [set_default_toolchain: Sets the default toolchain name.](#func_set_default_toolchain)
+    *   [set_defaults: Set default values for a target type.](#func_set_defaults)
+    *   [split_list: Splits a list into N different sub-lists.](#func_split_list)
+    *   [string_join: Concatenates a list of strings with a separator.](#func_string_join)
+    *   [string_replace: Replaces substring in the given string.](#func_string_replace)
+    *   [string_split: Split string into a list of strings.](#func_string_split)
+    *   [template: Define a template rule.](#func_template)
+    *   [tool: Specify arguments to a toolchain tool.](#func_tool)
+    *   [toolchain: Defines a toolchain.](#func_toolchain)
+    *   [write_file: Write a file to disk.](#func_write_file)
+*   [Built-in predefined variables](#predefined_variables)
+    *   [current_cpu: [string] The processor architecture of the current toolchain.](#var_current_cpu)
+    *   [current_os: [string] The operating system of the current toolchain.](#var_current_os)
+    *   [current_toolchain: [string] Label of the current toolchain.](#var_current_toolchain)
+    *   [default_toolchain: [string] Label of the default toolchain.](#var_default_toolchain)
+    *   [gn_version: [number] The version of gn.](#var_gn_version)
+    *   [host_cpu: [string] The processor architecture that GN is running on.](#var_host_cpu)
+    *   [host_os: [string] The operating system that GN is running on.](#var_host_os)
+    *   [invoker: [string] The invoking scope inside a template.](#var_invoker)
+    *   [python_path: [string] Absolute path of Python.](#var_python_path)
+    *   [root_build_dir: [string] Directory where build commands are run.](#var_root_build_dir)
+    *   [root_gen_dir: [string] Directory for the toolchain's generated files.](#var_root_gen_dir)
+    *   [root_out_dir: [string] Root directory for toolchain output files.](#var_root_out_dir)
+    *   [target_cpu: [string] The desired cpu architecture for the build.](#var_target_cpu)
+    *   [target_gen_dir: [string] Directory for a target's generated files.](#var_target_gen_dir)
+    *   [target_name: [string] The name of the current target.](#var_target_name)
+    *   [target_os: [string] The desired operating system for the build.](#var_target_os)
+    *   [target_out_dir: [string] Directory for target output files.](#var_target_out_dir)
+*   [Variables you set in targets](#target_variables)
+    *   [aliased_deps: [scope] Set of crate-dependency pairs.](#var_aliased_deps)
+    *   [all_dependent_configs: [label list] Configs to be forced on dependents.](#var_all_dependent_configs)
+    *   [allow_circular_includes_from: [label list] Permit includes from deps.](#var_allow_circular_includes_from)
+    *   [arflags: [string list] Arguments passed to static_library archiver.](#var_arflags)
+    *   [args: [string list] Arguments passed to an action.](#var_args)
+    *   [asmflags: [string list] Flags passed to the assembler.](#var_asmflags)
+    *   [assert_no_deps: [label pattern list] Ensure no deps on these targets.](#var_assert_no_deps)
+    *   [bridge_header: [string] Path to C/Objective-C compatibility header.](#var_bridge_header)
+    *   [bundle_contents_dir: Expansion of {{bundle_contents_dir}} in create_bundle.](#var_bundle_contents_dir)
+    *   [bundle_deps_filter: [label list] A list of labels that are filtered out.](#var_bundle_deps_filter)
+    *   [bundle_executable_dir: Expansion of {{bundle_executable_dir}} in create_bundle](#var_bundle_executable_dir)
+    *   [bundle_resources_dir: Expansion of {{bundle_resources_dir}} in create_bundle.](#var_bundle_resources_dir)
+    *   [bundle_root_dir: Expansion of {{bundle_root_dir}} in create_bundle.](#var_bundle_root_dir)
+    *   [cflags: [string list] Flags passed to all C compiler variants.](#var_cflags)
+    *   [cflags_c: [string list] Flags passed to the C compiler.](#var_cflags_c)
+    *   [cflags_cc: [string list] Flags passed to the C++ compiler.](#var_cflags_cc)
+    *   [cflags_objc: [string list] Flags passed to the Objective C compiler.](#var_cflags_objc)
+    *   [cflags_objcc: [string list] Flags passed to the Objective C++ compiler.](#var_cflags_objcc)
+    *   [check_includes: [boolean] Controls whether a target's files are checked.](#var_check_includes)
+    *   [code_signing_args: [string list] Arguments passed to code signing script.](#var_code_signing_args)
+    *   [code_signing_outputs: [file list] Output files for code signing step.](#var_code_signing_outputs)
+    *   [code_signing_script: [file name] Script for code signing.](#var_code_signing_script)
+    *   [code_signing_sources: [file list] Sources for code signing step.](#var_code_signing_sources)
+    *   [complete_static_lib: [boolean] Links all deps into a static library.](#var_complete_static_lib)
+    *   [configs: [label list] Configs applying to this target or config.](#var_configs)
+    *   [contents: Contents to write to file.](#var_contents)
+    *   [crate_name: [string] The name for the compiled crate.](#var_crate_name)
+    *   [crate_root: [string] The root source file for a binary or library.](#var_crate_root)
+    *   [crate_type: [string] The type of linkage to use on a shared_library.](#var_crate_type)
+    *   [data: [file list] Runtime data file dependencies.](#var_data)
+    *   [data_deps: [label list] Non-linked dependencies.](#var_data_deps)
+    *   [data_keys: [string list] Keys from which to collect metadata.](#var_data_keys)
+    *   [defines: [string list] C preprocessor defines.](#var_defines)
+    *   [depfile: [string] File name for input dependencies for actions.](#var_depfile)
+    *   [deps: [label list] Private linked dependencies.](#var_deps)
+    *   [externs: [scope] Set of Rust crate-dependency pairs.](#var_externs)
+    *   [framework_dirs: [directory list] Additional framework search directories.](#var_framework_dirs)
+    *   [frameworks: [name list] Name of frameworks that must be linked.](#var_frameworks)
+    *   [friend: [label pattern list] Allow targets to include private headers.](#var_friend)
+    *   [include_dirs: [directory list] Additional include directories.](#var_include_dirs)
+    *   [inputs: [file list] Additional compile-time dependencies.](#var_inputs)
+    *   [ldflags: [string list] Flags passed to the linker.](#var_ldflags)
+    *   [lib_dirs: [directory list] Additional library directories.](#var_lib_dirs)
+    *   [libs: [string list] Additional libraries to link.](#var_libs)
+    *   [metadata: [scope] Metadata of this target.](#var_metadata)
+    *   [module_name: [string] The name for the compiled module.](#var_module_name)
+    *   [output_conversion: Data format for generated_file targets.](#var_output_conversion)
+    *   [output_dir: [directory] Directory to put output file in.](#var_output_dir)
+    *   [output_extension: [string] Value to use for the output's file extension.](#var_output_extension)
+    *   [output_name: [string] Name for the output file other than the default.](#var_output_name)
+    *   [output_prefix_override: [boolean] Don't use prefix for output name.](#var_output_prefix_override)
+    *   [outputs: [file list] Output files for actions and copy targets.](#var_outputs)
+    *   [partial_info_plist: [filename] Path plist from asset catalog compiler.](#var_partial_info_plist)
+    *   [pool: [string] Label of the pool used by the action.](#var_pool)
+    *   [precompiled_header: [string] Header file to precompile.](#var_precompiled_header)
+    *   [precompiled_header_type: [string] "gcc" or "msvc".](#var_precompiled_header_type)
+    *   [precompiled_source: [file name] Source file to precompile.](#var_precompiled_source)
+    *   [product_type: [string] Product type for Xcode projects.](#var_product_type)
+    *   [public: [file list] Declare public header files for a target.](#var_public)
+    *   [public_configs: [label list] Configs applied to dependents.](#var_public_configs)
+    *   [public_deps: [label list] Declare public dependencies.](#var_public_deps)
+    *   [rebase: [boolean] Rebase collected metadata as files.](#var_rebase)
+    *   [response_file_contents: [string list] Contents of .rsp file for actions.](#var_response_file_contents)
+    *   [script: [file name] Script file for actions.](#var_script)
+    *   [sources: [file list] Source files for a target.](#var_sources)
+    *   [swiftflags: [string list] Flags passed to the swift compiler.](#var_swiftflags)
+    *   [testonly: [boolean] Declares a target must only be used for testing.](#var_testonly)
+    *   [visibility: [label list] A list of labels that can depend on a target.](#var_visibility)
+    *   [walk_keys: [string list] Key(s) for managing the metadata collection walk.](#var_walk_keys)
+    *   [weak_frameworks: [name list] Name of frameworks that must be weak linked.](#var_weak_frameworks)
+    *   [write_runtime_deps: Writes the target's runtime_deps to the given path.](#var_write_runtime_deps)
+    *   [xcasset_compiler_flags: [string list] Flags passed to xcassets compiler](#var_xcasset_compiler_flags)
+    *   [xcode_extra_attributes: [scope] Extra attributes for Xcode projects.](#var_xcode_extra_attributes)
+    *   [xcode_test_application_name: [string] Name for Xcode test target.](#var_xcode_test_application_name)
+*   [Other help topics](#other)
+    *   all: Print all the help at once
+    *   [buildargs: How build arguments work.](#buildargs)
+    *   [dotfile: Info about the toplevel .gn file.](#dotfile)
+    *   [execution: Build graph and execution overview.](#execution)
+    *   [grammar: Language and grammar for GN build files.](#grammar)
+    *   [input_conversion: Processing input from exec_script and read_file.](#io_conversion)
+    *   [file_pattern: Matching more than one file.](#file_pattern)
+    *   [label_pattern: Matching more than one label.](#label_pattern)
+    *   [labels: About labels.](#labels)
+    *   [metadata_collection: About metadata and its collection.](#metadata_collection)
+    *   [ninja_rules: How Ninja build rules are named.](#ninja_rules)
+    *   [nogncheck: Annotating includes for checking.](#nogncheck)
+    *   [output_conversion: Specifies how to transform a value to output.](#io_conversion)
+    *   [runtime_deps: How runtime dependency computation works.](#runtime_deps)
+    *   [source_expansion: Map sources to outputs for scripts.](#source_expansion)
+    *   [switches: Show available command-line switches.](#switch_list)
+
+## <a name="commands"></a>Commands
+
+### <a name="cmd_analyze"></a>**gn analyze &lt;out_dir&gt; &lt;input_path&gt; &lt;output_path&gt;**
+
+```
+  Analyze which targets are affected by a list of files.
+
+  This command takes three arguments:
+
+  out_dir is the path to the build directory.
+
+  input_path is a path to a file containing a JSON object with three fields:
+
+   - "files": A list of the filenames to check.
+
+   - "test_targets": A list of the labels for targets that are needed to run
+     the tests we wish to run.
+
+   - "additional_compile_targets": A list of the labels for targets that we
+     wish to rebuild, but aren't necessarily needed for testing. The important
+     difference between this field and "test_targets" is that if an item in
+     the additional_compile_targets list refers to a group, then any
+     dependencies of that group will be returned if they are out of date, but
+     the group itself does not need to be. If the dependencies themselves are
+     groups, the same filtering is repeated. This filtering can be used to
+     avoid rebuilding dependencies of a group that are unaffected by the input
+     files. The list may also contain the string "all" to refer to a
+     pseudo-group that contains every root target in the build graph.
+
+     This filtering behavior is also known as "pruning" the list of compile
+     targets.
+
+  If input_path is -, input is read from stdin.
+
+  output_path is a path indicating where the results of the command are to be
+  written. The results will be a file containing a JSON object with one or more
+  of following fields:
+
+   - "compile_targets": A list of the labels derived from the input
+     compile_targets list that are affected by the input files. Due to the way
+     the filtering works for compile targets as described above, this list may
+     contain targets that do not appear in the input list.
+
+   - "test_targets": A list of the labels from the input test_targets list that
+     are affected by the input files. This list will be a proper subset of the
+     input list.
+
+   - "invalid_targets": A list of any names from the input that do not exist in
+     the build graph. If this list is non-empty, the "error" field will also be
+     set to "Invalid targets".
+
+   - "status": A string containing one of three values:
+
+       - "Found dependency"
+       - "No dependency"
+       - "Found dependency (all)"
+
+     In the first case, the lists returned in compile_targets and test_targets
+     should be passed to ninja to build. In the second case, nothing was
+     affected and no build is necessary. In the third case, GN could not
+     determine the correct answer and returned the input as the output in order
+     to be safe.
+
+   - "error": This will only be present if an error occurred, and will contain
+     a string describing the error. This includes cases where the input file is
+     not in the right format, or contains invalid targets.
+
+  If output_path is -, output is written to stdout.
+
+  The command returns 1 if it is unable to read the input file or write the
+  output file, or if there is something wrong with the build such that gen
+  would also fail, and 0 otherwise. In particular, it returns 0 even if the
+  "error" key is non-empty and a non-fatal error occurred. In other words, it
+  tries really hard to always write something to the output JSON and convey
+  errors that way rather than via return codes.
+```
+### <a name="cmd_args"></a>**gn args**: (command-line tool)
+
+```
+  Display or configure arguments declared by the build.
+
+    gn args <out_dir> [--list] [--short] [--args] [--overrides-only]
+
+  See also "gn help buildargs" for a more high-level overview of how
+  build arguments work.
+```
+
+#### **Usage**
+
+```
+  gn args <out_dir>
+      Open the arguments for the given build directory in an editor. If the
+      given build directory doesn't exist, it will be created and an empty args
+      file will be opened in the editor. You would type something like this
+      into that file:
+          enable_doom_melon=false
+          os="android"
+
+      To find your editor on Posix, GN will search the environment variables in
+      order: GN_EDITOR, VISUAL, and EDITOR. On Windows GN will open the command
+      associated with .txt files.
+
+      Note: you can edit the build args manually by editing the file "args.gn"
+      in the build directory and then running "gn gen <out_dir>".
+
+  gn args <out_dir> --list[=<exact_arg>] [--short] [--overrides-only] [--json]
+      Lists all build arguments available in the current configuration, or, if
+      an exact_arg is specified for the list flag, just that one build
+      argument.
+
+      The output will list the declaration location, current value for the
+      build, default value (if different than the current value), and comment
+      preceding the declaration.
+
+      If --short is specified, only the names and current values will be
+      printed.
+
+      If --overrides-only is specified, only the names and current values of
+      arguments that have been overridden (i.e. non-default arguments) will
+      be printed. Overrides come from the <out_dir>/args.gn file and //.gn
+
+      If --json is specified, the output will be emitted in json format.
+      JSON schema for output:
+      [
+        {
+          "name": variable_name,
+          "current": {
+            "value": overridden_value,
+            "file": file_name,
+            "line": line_no
+          },
+          "default": {
+            "value": default_value,
+            "file": file_name,
+            "line": line_no
+          },
+          "comment": comment_string
+        },
+        ...
+      ]
+```
+
+#### **Examples**
+
+```
+  gn args out/Debug
+    Opens an editor with the args for out/Debug.
+
+  gn args out/Debug --list --short
+    Prints all arguments with their default values for the out/Debug
+    build.
+
+  gn args out/Debug --list --short --overrides-only
+    Prints overridden arguments for the out/Debug build.
+
+  gn args out/Debug --list=target_cpu
+    Prints information about the "target_cpu" argument for the "
+   "out/Debug
+    build.
+
+  gn args --list --args="os=\"android\" enable_doom_melon=true"
+    Prints all arguments with the default values for a build with the
+    given arguments set (which may affect the values of other
+    arguments).
+```
+### <a name="cmd_check"></a>**gn check &lt;out_dir&gt; [&lt;label_pattern&gt;] [\--force] [\--check-generated]**
+
+```
+  GN's include header checker validates that the includes for C-like source
+  files match the build dependency graph.
+
+  "gn check" is the same thing as "gn gen" with the "--check" flag except that
+  this command does not write out any build files. It's intended to be an easy
+  way to manually trigger include file checking.
+
+  The <label_pattern> can take exact labels or patterns that match more than
+  one (although not general regular expressions). If specified, only those
+  matching targets will be checked. See "gn help label_pattern" for details.
+```
+
+#### **Command-specific switches**
+
+```
+  --check-generated
+      Generated files are normally not checked since they do not exist
+      until after a build. With this flag, those generated files that
+      can be found on disk are also checked.
+
+  --check-system
+     Check system style includes (using <angle brackets>) in addition to
+     "double quote" includes.
+
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
+  --force
+      Ignores specifications of "check_includes = false" and checks all
+      target's files that match the target label.
+```
+
+#### **What gets checked**
+
+```
+  The .gn file may specify a list of targets to be checked in the list
+  check_targets (see "gn help dotfile"). Alternatively, the .gn file may
+  specify a list of targets not to be checked in no_check_targets. If a label
+  pattern is specified on the command line, neither check_targets or
+  no_check_targets is used.
+
+  Targets can opt-out from checking with "check_includes = false" (see
+  "gn help check_includes").
+
+  For targets being checked:
+
+    - GN opens all C-like source files in the targets to be checked and scans
+      the top for includes.
+
+    - Generated files (that might not exist yet) are ignored unless
+      the --check-generated flag is provided.
+
+    - Includes with a "nogncheck" annotation are skipped (see
+      "gn help nogncheck").
+
+    - Includes using "quotes" are always checked.
+        If system style checking is enabled, includes using <angle brackets>
+        are also checked.
+
+    - Include paths are assumed to be relative to any of the "include_dirs" for
+      the target (including the implicit current dir).
+
+    - GN does not run the preprocessor so will not understand conditional
+      includes.
+
+    - Only includes matching known files in the build are checked: includes
+      matching unknown paths are ignored.
+
+  For an include to be valid:
+
+    - The included file must be in the current target, or there must be a path
+      following only public dependencies to a target with the file in it
+      ("gn path" is a good way to diagnose problems).
+
+    - There can be multiple targets with an included file: only one needs to be
+      valid for the include to be allowed.
+
+    - If there are only "sources" in a target, all are considered to be public
+      and can be included by other targets with a valid public dependency path.
+
+    - If a target lists files as "public", only those files are able to be
+      included by other targets. Anything in the sources will be considered
+      private and will not be includable regardless of dependency paths.
+
+    - Outputs from actions are treated like public sources on that target.
+
+    - A target can include headers from a target that depends on it if the
+      other target is annotated accordingly. See "gn help
+      allow_circular_includes_from".
+```
+
+#### **Advice on fixing problems**
+
+```
+  If you have a third party project that is difficult to fix or doesn't care
+  about include checks it's generally best to exclude that target from checking
+  altogether via "check_includes = false".
+
+  If you have conditional includes, make sure the build conditions and the
+  preprocessor conditions match, and annotate the line with "nogncheck" (see
+  "gn help nogncheck" for an example).
+
+  If two targets are hopelessly intertwined, use the
+  "allow_circular_includes_from" annotation. Ideally each should have identical
+  dependencies so configs inherited from those dependencies are consistent (see
+  "gn help allow_circular_includes_from").
+
+  If you have a standalone header file or files that need to be shared between
+  a few targets, you can consider making a source_set listing only those
+  headers as public sources. With only header files, the source set will be a
+  no-op from a build perspective, but will give a central place to refer to
+  those headers. That source set's files will still need to pass "gn check" in
+  isolation.
+
+  In rare cases it makes sense to list a header in more than one target if it
+  could be considered conceptually a member of both.
+```
+
+#### **Examples**
+
+```
+  gn check out/Debug
+      Check everything.
+
+  gn check out/Default //foo:bar
+      Check only the files in the //foo:bar target.
+
+  gn check out/Default "//foo/*
+      Check only the files in targets in the //foo directory tree.
+```
+### <a name="cmd_clean"></a>**gn clean &lt;out_dir&gt;...**
+
+```
+  Deletes the contents of the output directory except for args.gn and
+  creates a Ninja build environment sufficient to regenerate the build.
+```
+### <a name="cmd_clean_stale"></a>**gn clean_stale [\--ninja-executable=...] &lt;out_dir&gt;...**
+
+```
+  Removes the no longer needed output files from the build directory and prunes
+  their records from the ninja build log and dependency database. These are
+  output files that were generated from previous builds, but the current build
+  graph no longer references them.
+
+  This command requires a ninja executable of at least version 1.10.0. The
+  executable must be provided by the --ninja-executable switch.
+```
+
+#### **Options**
+
+```
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use.
+```
+### <a name="cmd_desc"></a>**gn desc**
+
+```
+  gn desc <out_dir> <label or pattern> [<what to show>] [--blame]
+          [--format=json]
+
+  Displays information about a given target or config. The build parameters
+  will be taken for the build in the given <out_dir>.
+
+  The <label or pattern> can be a target label, a config label, or a label
+  pattern (see "gn help label_pattern"). A label pattern will only match
+  targets.
+```
+
+#### **Possibilities for &lt;what to show&gt;**
+
+```
+  (If unspecified an overall summary will be displayed.)
+
+  all_dependent_configs
+  allow_circular_includes_from
+  arflags [--blame]
+  args
+  cflags [--blame]
+  cflags_c [--blame]
+  cflags_cc [--blame]
+  check_includes
+  configs [--tree] (see below)
+  data_keys
+  defines [--blame]
+  depfile
+  deps [--all] [--tree] (see below)
+  framework_dirs
+  frameworks
+  include_dirs [--blame]
+  inputs
+  ldflags [--blame]
+  lib_dirs
+  libs
+  metadata
+  output_conversion
+  outputs
+  public_configs
+  public
+  rebase
+  script
+  sources
+  testonly
+  visibility
+  walk_keys
+  weak_frameworks
+
+  runtime_deps
+      Compute all runtime deps for the given target. This is a computed list
+      and does not correspond to any GN variable, unlike most other values
+      here.
+
+      The output is a list of file names relative to the build directory. See
+      "gn help runtime_deps" for how this is computed. This also works with
+      "--blame" to see the source of the dependency.
+```
+
+#### **Shared flags**
+
+```
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
+  --format=json
+      Format the output as JSON instead of text.
+```
+
+#### **Target flags**
+
+```
+  --blame
+      Used with any value specified on a config, this will name the config that
+      causes that target to get the flag. This doesn't currently work for libs,
+      lib_dirs, frameworks, weak_frameworks and framework_dirs because those are
+      inherited and are more complicated to figure out the blame (patches
+      welcome).
+```
+
+#### **Configs**
+
+```
+  The "configs" section will list all configs that apply. For targets this will
+  include configs specified in the "configs" variable of the target, and also
+  configs pushed onto this target via public or "all dependent" configs.
+
+  Configs can have child configs. Specifying --tree will show the hierarchy.
+```
+
+#### **Printing outputs**
+
+```
+  The "outputs" section will list all outputs that apply, including the outputs
+  computed from the tool definition (eg for "executable", "static_library", ...
+  targets).
+```
+
+#### **Printing deps**
+
+```
+  Deps will include all public, private, and data deps (TODO this could be
+  clarified and enhanced) sorted in order applying. The following may be used:
+
+  --all
+      Collects all recursive dependencies and prints a sorted flat list. Also
+      usable with --tree (see below).
+
+  --as=(buildfile|label|output)
+      How to print targets.
+
+      buildfile
+          Prints the build files where the given target was declared as
+          file names.
+      label  (default)
+          Prints the label of the target.
+      output
+          Prints the first output file for the target relative to the
+          root build directory.
+
+  --testonly=(true|false)
+      Restrict outputs to targets with the testonly flag set
+      accordingly. When unspecified, the target's testonly flags are
+      ignored.
+
+  --tree
+      Print a dependency tree. By default, duplicates will be elided with "..."
+      but when --all and -tree are used together, no eliding will be performed.
+
+      The "deps", "public_deps", and "data_deps" will all be included in the
+      tree.
+
+      Tree output can not be used with the filtering or output flags: --as,
+      --type, --testonly.
+
+  --type=(action|copy|executable|group|loadable_module|shared_library|
+          source_set|static_library)
+      Restrict outputs to targets matching the given type. If
+      unspecified, no filtering will be performed.
+```
+
+#### **Note**
+
+```
+  This command will show the full name of directories and source files, but
+  when directories and source paths are written to the build file, they will be
+  adjusted to be relative to the build directory. So the values for paths
+  displayed by this command won't match (but should mean the same thing).
+```
+
+#### **Examples**
+
+```
+  gn desc out/Debug //base:base
+      Summarizes the given target.
+
+  gn desc out/Foo :base_unittests deps --tree
+      Shows a dependency tree of the "base_unittests" project in
+      the current directory.
+
+  gn desc out/Debug //base defines --blame
+      Shows defines set for the //base:base target, annotated by where
+      each one was set from.
+```
+### <a name="cmd_format"></a>**gn format [\--dump-tree] (\--stdin | &lt;list of build_files...&gt;)**
+
+```
+  Formats .gn file to a standard format.
+
+  The contents of some lists ('sources', 'deps', etc.) will be sorted to a
+  canonical order. To suppress this, you can add a comment of the form "#
+  NOSORT" immediately preceding the assignment. e.g.
+
+  # NOSORT
+  sources = [
+    "z.cc",
+    "a.cc",
+  ]
+```
+
+#### **Arguments**
+
+```
+  --dry-run
+      Prints the list of files that would be reformatted but does not write
+      anything to disk. This is useful for presubmit/lint-type checks.
+      - Exit code 0: successful format, matches on disk.
+      - Exit code 1: general failure (parse error, etc.)
+      - Exit code 2: successful format, but differs from on disk.
+
+  --dump-tree[=( text | json )]
+      Dumps the parse tree to stdout and does not update the file or print
+      formatted output. If no format is specified, text format will be used.
+
+  --stdin
+      Read input from stdin and write to stdout rather than update a file
+      in-place.
+
+  --read-tree=json
+      Reads an AST from stdin in the format output by --dump-tree=json and
+      uses that as the parse tree. (The only read-tree format currently
+      supported is json.) The given .gn file will be overwritten. This can be
+      used to programmatically transform .gn files.
+```
+
+#### **Examples**
+```
+  gn format //some/BUILD.gn //some/other/BUILD.gn //and/another/BUILD.gn
+  gn format some\\BUILD.gn
+  gn format /abspath/some/BUILD.gn
+  gn format --stdin
+  gn format --read-tree=json //rewritten/BUILD.gn
+```
+### <a name="cmd_gen"></a>**gn gen [\--check] [&lt;ide options&gt;] &lt;out_dir&gt;**
+
+```
+  Generates ninja files from the current tree and puts them in the given output
+  directory.
+
+  The output directory can be a source-repo-absolute path name such as:
+      //out/foo
+  Or it can be a directory relative to the current directory such as:
+      out/foo
+
+  "gn gen --check" is the same as running "gn check". "gn gen --check=system" is
+  the same as running "gn check --check-system".  See "gn help check" for
+  documentation on that mode.
+
+  See "gn help switches" for the common command-line switches.
+```
+
+#### **General options**
+
+```
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use. This executable will
+      be used as an IDE option to indicate which ninja to use for building. This
+      executable will also be used as part of the gen process for triggering a
+      restat on generated ninja files and for use with --clean-stale.
+
+  --clean-stale
+      This option will cause no longer needed output files to be removed from
+      the build directory, and their records pruned from the ninja build log and
+      dependency database after the ninja build graph has been generated. This
+      option requires a ninja executable of at least version 1.10.0. It can be
+      provided by the --ninja-executable switch. Also see "gn help clean_stale".
+```
+
+#### **IDE options**
+
+```
+  GN optionally generates files for IDE. Files won't be overwritten if their
+  contents don't change. Possibilities for <ide options>
+
+  --ide=<ide_name>
+      Generate files for an IDE. Currently supported values:
+      "eclipse" - Eclipse CDT settings file.
+      "vs" - Visual Studio project/solution files.
+             (default Visual Studio version: 2019)
+      "vs2013" - Visual Studio 2013 project/solution files.
+      "vs2015" - Visual Studio 2015 project/solution files.
+      "vs2017" - Visual Studio 2017 project/solution files.
+      "vs2019" - Visual Studio 2019 project/solution files.
+      "xcode" - Xcode workspace/solution files.
+      "qtcreator" - QtCreator project files.
+      "json" - JSON file containing target information
+
+  --filters=<path_prefixes>
+      Semicolon-separated list of label patterns used to limit the set of
+      generated projects (see "gn help label_pattern"). Only matching targets
+      and their dependencies will be included in the solution. Only used for
+      Visual Studio, Xcode and JSON.
+```
+
+#### **Visual Studio Flags**
+
+```
+  --sln=<file_name>
+      Override default sln file name ("all"). Solution file is written to the
+      root build directory.
+
+  --no-deps
+      Don't include targets dependencies to the solution. Changes the way how
+      --filters option works. Only directly matching targets are included.
+
+  --winsdk=<sdk_version>
+      Use the specified Windows 10 SDK version to generate project files.
+      As an example, "10.0.15063.0" can be specified to use Creators Update SDK
+      instead of the default one.
+
+  --ninja-extra-args=<string>
+      This string is passed without any quoting to the ninja invocation
+      command-line. Can be used to configure ninja flags, like "-j".
+```
+
+#### **Xcode Flags**
+
+```
+  --xcode-project=<file_name>
+      Override default Xcode project file name ("all"). The project file is
+      written to the root build directory.
+
+  --xcode-build-system=<value>
+      Configure the build system to use for the Xcode project. Supported
+      values are (default to "legacy"):
+      "legacy" - Legacy Build system
+      "new" - New Build System
+
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use when building.
+
+  --ninja-extra-args=<string>
+      This string is passed without any quoting to the ninja invocation
+      command-line. Can be used to configure ninja flags, like "-j".
+
+  --ide-root-target=<target_name>
+      Name of the target corresponding to "All" target in Xcode. If unset,
+      "All" invokes ninja without any target and builds everything.
+```
+
+#### **QtCreator Flags**
+
+```
+  --ide-root-target=<target_name>
+      Name of the root target for which the QtCreator project will be generated
+      to contain files of it and its dependencies. If unset, the whole build
+      graph will be emitted.
+```
+
+#### **Eclipse IDE Support**
+
+```
+  GN DOES NOT generate Eclipse CDT projects. Instead, it generates a settings
+  file which can be imported into an Eclipse CDT project. The XML file contains
+  a list of include paths and defines. Because GN does not generate a full
+  .cproject definition, it is not possible to properly define includes/defines
+  for each file individually. Instead, one set of includes/defines is generated
+  for the entire project. This works fairly well but may still result in a few
+  indexer issues here and there.
+```
+
+#### **Generic JSON Output**
+
+```
+  Dumps target information to a JSON file and optionally invokes a
+  python script on the generated file. See the comments at the beginning
+  of json_project_writer.cc and desc_builder.cc for an overview of the JSON
+  file format.
+
+  --json-file-name=<json_file_name>
+      Overrides default file name (project.json) of generated JSON file.
+
+  --json-ide-script=<path_to_python_script>
+      Executes python script after the JSON file is generated or updated with
+      new content. Path can be project absolute (//), system absolute (/) or
+      relative, in which case the output directory will be base. Path to
+      generated JSON file will be first argument when invoking script.
+
+  --json-ide-script-args=<argument>
+      Optional second argument that will passed to executed script.
+```
+
+#### **Compilation Database**
+
+```
+  --export-rust-project
+      Produces a rust-project.json file in the root of the build directory
+      This is used for various tools in the Rust ecosystem allowing for the
+      replay of individual compilations independent of the build system.
+      This is an unstable format and likely to change without warning.
+
+  --export-compile-commands[=<target_name1,target_name2...>]
+      Produces a compile_commands.json file in the root of the build directory
+      containing an array of “command objects”, where each command object
+      specifies one way a translation unit is compiled in the project. If a list
+      of target_name is supplied, only targets that are reachable from any
+      target in any build file whose name is target_name will be used for
+      “command objects” generation, otherwise all available targets will be used.
+      This is used for various Clang-based tooling, allowing for the replay of
+      individual compilations independent of the build system.
+      e.g. "foo" will match:
+      - "//path/to/src:foo"
+      - "//other/path:foo"
+      - "//foo:foo"
+      and not match:
+      - "//foo:bar"
+```
+### <a name="cmd_help"></a>**gn help &lt;anything&gt;**
+
+```
+  Yo dawg, I heard you like help on your help so I put help on the help in the
+  help.
+
+  You can also use "all" as the parameter to get all help at once.
+```
+
+#### **Switches**
+
+```
+  --markdown
+      Format output in markdown syntax.
+```
+
+#### **Example**
+
+```
+  gn help --markdown all
+      Dump all help to stdout in markdown format.
+```
+### <a name="cmd_ls"></a>**gn ls &lt;out_dir&gt; [&lt;label_pattern&gt;] [\--default-toolchain] [\--as=...]**
+```
+      [--type=...] [--testonly=...]
+
+  Lists all targets matching the given pattern for the given build directory.
+  By default, only targets in the default toolchain will be matched unless a
+  toolchain is explicitly supplied.
+
+  If the label pattern is unspecified, list all targets. The label pattern is
+  not a general regular expression (see "gn help label_pattern"). If you need
+  more complex expressions, pipe the result through grep.
+```
+
+#### **Options**
+
+```
+  --as=(buildfile|label|output)
+      How to print targets.
+
+      buildfile
+          Prints the build files where the given target was declared as
+          file names.
+      label  (default)
+          Prints the label of the target.
+      output
+          Prints the first output file for the target relative to the
+          root build directory.
+
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
+  --testonly=(true|false)
+      Restrict outputs to targets with the testonly flag set
+      accordingly. When unspecified, the target's testonly flags are
+      ignored.
+
+  --type=(action|copy|executable|group|loadable_module|shared_library|
+          source_set|static_library)
+      Restrict outputs to targets matching the given type. If
+      unspecified, no filtering will be performed.
+```
+
+#### **Examples**
+
+```
+  gn ls out/Debug
+      Lists all targets in the default toolchain.
+
+  gn ls out/Debug "//base/*"
+      Lists all targets in the directory base and all subdirectories.
+
+  gn ls out/Debug "//base:*"
+      Lists all targets defined in //base/BUILD.gn.
+
+  gn ls out/Debug //base --as=output
+      Lists the build output file for //base:base
+
+  gn ls out/Debug --type=executable
+      Lists all executables produced by the build.
+
+  gn ls out/Debug "//base/*" --as=output | xargs ninja -C out/Debug
+      Builds all targets in //base and all subdirectories.
+```
+### <a name="cmd_meta"></a>**gn meta**
+
+```
+  gn meta <out_dir> <target>* --data=<key>[,<key>*]* [--walk=<key>[,<key>*]*]
+          [--rebase=<dest dir>]
+
+  Lists collected metaresults of all given targets for the given data key(s),
+  collecting metadata dependencies as specified by the given walk key(s).
+
+  See `gn help generated_file` for more information on the walk.
+```
+
+#### **Arguments**
+
+```
+  <target(s)>
+    A list of target labels from which to initiate the walk.
+
+  --data
+    A list of keys from which to extract data. In each target walked, its metadata
+    scope is checked for the presence of these keys. If present, the contents of
+    those variable in the scope are appended to the results list.
+
+  --walk (optional)
+    A list of keys from which to control the walk. In each target walked, its
+    metadata scope is checked for the presence of any of these keys. If present,
+    the contents of those variables is checked to ensure that it is a label of
+    a valid dependency of the target and then added to the set of targets to walk.
+    If the empty string ("") is present in any of these keys, all deps and data_deps
+    are added to the walk set.
+
+  --rebase (optional)
+    A destination directory onto which to rebase any paths found. If set, all
+    collected metadata will be rebased onto this path. This option will throw errors
+    if collected metadata is not a list of strings.
+```
+
+#### **Examples**
+
+```
+  gn meta out/Debug "//base/foo" --data=files
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --data=other
+      Lists collected metaresults for the `files` and `other` keys in the
+      //base/foo:foo target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --walk=stop
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of the dependencies listed in the `stop` key (and so on).
+
+  gn meta out/Debug "//base/foo" --data=files --rebase="/"
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree, rebasing the strings in the `files`
+      key onto the source directory of the target's declaration relative to "/".
+```
+### <a name="cmd_outputs"></a>**gn outputs &lt;out_dir&gt; &lt;list of target or file names...&gt;**
+
+```
+  Lists the output files corresponding to the given target(s) or file name(s).
+  There can be multiple outputs because there can be more than one output
+  generated by a build step, and there can be more than one toolchain matched.
+  You can also list multiple inputs which will generate a union of all the
+  outputs from those inputs.
+
+   - The input target/file names are relative to the current directory.
+
+   - The output file names are relative to the root build directory.
+
+   This command is useful for finding a ninja command that will build only a
+   portion of the build.
+```
+
+#### **Target outputs**
+
+```
+  If the parameter is a target name that includes a toolchain, it will match
+  only that target in that toolchain. If no toolchain is specified, it will
+  match all targets with that name in any toolchain.
+
+  The result will be the outputs specified by that target which could be a
+  library, executable, output of an action, a stamp file, etc.
+```
+
+#### **File outputs**
+
+```
+  If the parameter is a file name it will compute the output for that compile
+  step for all targets in all toolchains that contain that file as a source
+  file.
+
+  If the source is not compiled (e.g. a header or text file), the command will
+  produce no output.
+
+  If the source is listed as an "input" to a binary target or action will
+  resolve to that target's outputs.
+```
+
+#### **Example**
+
+```
+  gn outputs out/debug some/directory:some_target
+      Find the outputs of a given target.
+
+  gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
+      Compiles just the given source file in all toolchains it's referenced in.
+
+  git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
+      Compiles all files changed in git.
+```
+### <a name="cmd_path"></a>**gn path &lt;out_dir&gt; &lt;target_one&gt; &lt;target_two&gt;**
+
+```
+  Finds paths of dependencies between two targets. Each unique path will be
+  printed in one group, and groups will be separate by newlines. The two
+  targets can appear in either order (paths will be found going in either
+  direction).
+
+  By default, a single path will be printed. If there is a path with only
+  public dependencies, the shortest public path will be printed. Otherwise, the
+  shortest path using either public or private dependencies will be printed. If
+  --with-data is specified, data deps will also be considered. If there are
+  multiple shortest paths, an arbitrary one will be selected.
+```
+
+#### **Interesting paths**
+
+```
+  In a large project, there can be 100's of millions of unique paths between a
+  very high level and a common low-level target. To make the output more useful
+  (and terminate in a reasonable time), GN will not revisit sub-paths
+  previously known to lead to the target.
+```
+
+#### **Options**
+
+```
+  --all
+     Prints all "interesting" paths found rather than just the first one.
+     Public paths will be printed first in order of increasing length, followed
+     by non-public paths in order of increasing length.
+
+  --public
+     Considers only public paths. Can't be used with --with-data.
+
+  --with-data
+     Additionally follows data deps. Without this flag, only public and private
+     linked deps will be followed. Can't be used with --public.
+```
+
+#### **Example**
+
+```
+  gn path out/Default //base //gn
+```
+### <a name="cmd_refs"></a>**gn refs**
+
+```
+  gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* [--all]
+          [--default-toolchain] [--as=...] [--testonly=...] [--type=...]
+
+  Finds reverse dependencies (which targets reference something). The input is
+  a list containing:
+
+   - Target label: The result will be which targets depend on it.
+
+   - Config label: The result will be which targets list the given config in
+     its "configs" or "public_configs" list.
+
+   - Label pattern: The result will be which targets depend on any target
+     matching the given pattern. Patterns will not match configs. These are not
+     general regular expressions, see "gn help label_pattern" for details.
+
+   - File name: The result will be which targets list the given file in its
+     "inputs", "sources", "public", "data", or "outputs". Any input that does
+     not contain wildcards and does not match a target or a config will be
+     treated as a file.
+
+   - Response file: If the input starts with an "@", it will be interpreted as
+     a path to a file containing a list of labels or file names, one per line.
+     This allows us to handle long lists of inputs without worrying about
+     command line limits.
+```
+
+#### **Options**
+
+```
+  --all
+      When used without --tree, will recurse and display all unique
+      dependencies of the given targets. For example, if the input is a target,
+      this will output all targets that depend directly or indirectly on the
+      input. If the input is a file, this will output all targets that depend
+      directly or indirectly on that file.
+
+      When used with --tree, turns off eliding to show a complete tree.
+
+  --as=(buildfile|label|output)
+      How to print targets.
+
+      buildfile
+          Prints the build files where the given target was declared as
+          file names.
+      label  (default)
+          Prints the label of the target.
+      output
+          Prints the first output file for the target relative to the
+          root build directory.
+
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
+  -q
+     Quiet. If nothing matches, don't print any output. Without this option, if
+     there are no matches there will be an informational message printed which
+     might interfere with scripts processing the output.
+
+  --testonly=(true|false)
+      Restrict outputs to targets with the testonly flag set
+      accordingly. When unspecified, the target's testonly flags are
+      ignored.
+
+  --tree
+      Outputs a reverse dependency tree from the given target. Duplicates will
+      be elided. Combine with --all to see a full dependency tree.
+
+      Tree output can not be used with the filtering or output flags: --as,
+      --type, --testonly.
+
+  --type=(action|copy|executable|group|loadable_module|shared_library|
+          source_set|static_library)
+      Restrict outputs to targets matching the given type. If
+      unspecified, no filtering will be performed.
+```
+
+#### **Examples (target input)**
+
+```
+  gn refs out/Debug //gn:gn
+      Find all targets depending on the given exact target name.
+
+  gn refs out/Debug //base:i18n --as=buildfiles | xargs gvim
+      Edit all .gn files containing references to //base:i18n
+
+  gn refs out/Debug //base --all
+      List all targets depending directly or indirectly on //base:base.
+
+  gn refs out/Debug "//base/*"
+      List all targets depending directly on any target in //base or
+      its subdirectories.
+
+  gn refs out/Debug "//base:*"
+      List all targets depending directly on any target in
+      //base/BUILD.gn.
+
+  gn refs out/Debug //base --tree
+      Print a reverse dependency tree of //base:base
+```
+
+#### **Examples (file input)**
+
+```
+  gn refs out/Debug //base/macros.h
+      Print target(s) listing //base/macros.h as a source.
+
+  gn refs out/Debug //base/macros.h --tree
+      Display a reverse dependency tree to get to the given file. This
+      will show how dependencies will reference that file.
+
+  gn refs out/Debug //base/macros.h //base/at_exit.h --all
+      Display all unique targets with some dependency path to a target
+      containing either of the given files as a source.
+
+  gn refs out/Debug //base/macros.h --testonly=true --type=executable
+          --all --as=output
+      Display the executable file names of all test executables
+      potentially affected by a change to the given file.
+```
+## <a name="targets"></a>Target declarations
+
+### <a name="func_action"></a>**action**: Declare a target that runs a script a single time.
+
+```
+  This target type allows you to run a script a single time to produce one or
+  more output files. If you want to run a script once for each of a set of
+  input files, see "gn help action_foreach".
+```
+
+#### **Inputs**
+
+```
+  In an action the "sources" and "inputs" are treated the same: they're both
+  input dependencies on script execution with no special handling. If you want
+  to pass the sources to your script, you must do so explicitly by including
+  them in the "args". Note also that this means there is no special handling of
+  paths since GN doesn't know which of the args are paths and not. You will
+  want to use rebase_path() to convert paths to be relative to the
+  root_build_dir.
+
+  You can dynamically write input dependencies (for incremental rebuilds if an
+  input file changes) by writing a depfile when the script is run (see "gn help
+  depfile"). This is more flexible than "inputs".
+
+  If the command line length is very long, you can use response files to pass
+  args to your script. See "gn help response_file_contents".
+
+  It is recommended you put inputs to your script in the "sources" variable,
+  and stuff like other Python files required to run your script in the "inputs"
+  variable.
+
+  The "deps" and "public_deps" for an action will always be
+  completed before any part of the action is run so it can depend on
+  the output of previous steps. The "data_deps" will be built if the
+  action is built, but may not have completed before all steps of the
+  action are started. This can give additional parallelism in the build
+  for runtime-only dependencies.
+```
+
+#### **Outputs**
+
+```
+  You should specify files created by your script by specifying them in the
+  "outputs".
+
+  The script will be executed with the given arguments with the current
+  directory being that of the root build directory. If you pass files
+  to your script, see "gn help rebase_path" for how to convert
+  file names to be relative to the build directory (file names in the
+  sources, outputs, and inputs will be all treated as relative to the
+  current build file and converted as needed automatically).
+
+  GN sets Ninja's flag 'restat = 1` for all action commands. This means
+  that Ninja will check the timestamp of the output after the action
+  completes. If output timestamp is unchanged, the step will be treated
+  as if it never needed to be rebuilt, potentially eliminating some
+  downstream steps for incremental builds. Scripts can improve build
+  performance by taking care not to change the timstamp of the output
+  file(s) if the contents have not changed.
+```
+
+#### **File name handling**
+
+```
+  All output files must be inside the output directory of the build.
+  You would generally use |$target_out_dir| or |$target_gen_dir| to
+  reference the output or generated intermediate file directories,
+  respectively.
+```
+
+#### **Variables**
+
+```
+  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
+  response_file_contents, script*, sources
+  * = required
+```
+
+#### **Example**
+
+```
+  action("run_this_guy_once") {
+    script = "doprocessing.py"
+    sources = [ "my_configuration.txt" ]
+    outputs = [ "$target_gen_dir/insightful_output.txt" ]
+
+    # Our script imports this Python file so we want to rebuild if it changes.
+    inputs = [ "helper_library.py" ]
+
+    # Note that we have to manually pass the sources to our script if the
+    # script needs them as inputs.
+    args = [ "--out", rebase_path(target_gen_dir, root_build_dir) ] +
+           rebase_path(sources, root_build_dir)
+  }
+```
+### <a name="func_action_foreach"></a>**action_foreach**: Declare a target that runs a script over a set of files.
+
+```
+  This target type allows you to run a script once-per-file over a set of
+  sources. If you want to run a script once that takes many files as input, see
+  "gn help action".
+```
+
+#### **Inputs**
+
+```
+  The script will be run once per file in the "sources" variable. The "outputs"
+  variable should specify one or more files with a source expansion pattern in
+  it (see "gn help source_expansion"). The output file(s) for each script
+  invocation should be unique. Normally you use "{{source_name_part}}" in each
+  output file.
+
+  If your script takes additional data as input, such as a shared configuration
+  file or a Python module it uses, those files should be listed in the "inputs"
+  variable. These files are treated as dependencies of each script invocation.
+
+  If the command line length is very long, you can use response files to pass
+  args to your script. See "gn help response_file_contents".
+
+  You can dynamically write input dependencies (for incremental rebuilds if an
+  input file changes) by writing a depfile when the script is run (see "gn help
+  depfile"). This is more flexible than "inputs".
+
+  The "deps" and "public_deps" for an action will always be
+  completed before any part of the action is run so it can depend on
+  the output of previous steps. The "data_deps" will be built if the
+  action is built, but may not have completed before all steps of the
+  action are started. This can give additional parallelism in the build
+  for runtime-only dependencies.
+```
+
+#### **Outputs**
+
+```
+  The script will be executed with the given arguments with the current
+  directory being that of the root build directory. If you pass files
+  to your script, see "gn help rebase_path" for how to convert
+  file names to be relative to the build directory (file names in the
+  sources, outputs, and inputs will be all treated as relative to the
+  current build file and converted as needed automatically).
+
+  GN sets Ninja's flag 'restat = 1` for all action commands. This means
+  that Ninja will check the timestamp of the output after the action
+  completes. If output timestamp is unchanged, the step will be treated
+  as if it never needed to be rebuilt, potentially eliminating some
+  downstream steps for incremental builds. Scripts can improve build
+  performance by taking care not to change the timstamp of the output
+  file(s) if the contents have not changed.
+```
+
+#### **File name handling**
+
+```
+  All output files must be inside the output directory of the build.
+  You would generally use |$target_out_dir| or |$target_gen_dir| to
+  reference the output or generated intermediate file directories,
+  respectively.
+```
+
+#### **Variables**
+
+```
+  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
+  response_file_contents, script*, sources*
+  * = required
+```
+
+#### **Example**
+
+```
+  # Runs the script over each IDL file. The IDL script will generate both a .cc
+  # and a .h file for each input.
+  action_foreach("my_idl") {
+    script = "idl_processor.py"
+    sources = [ "foo.idl", "bar.idl" ]
+
+    # Our script reads this file each time, so we need to list it as a
+    # dependency so we can rebuild if it changes.
+    inputs = [ "my_configuration.txt" ]
+
+    # Transformation from source file name to output file names.
+    outputs = [ "$target_gen_dir/{{source_name_part}}.h",
+                "$target_gen_dir/{{source_name_part}}.cc" ]
+
+    # Note that since "args" is opaque to GN, if you specify paths here, you
+    # will need to convert it to be relative to the build directory using
+    # rebase_path().
+    args = [
+      "{{source}}",
+      "-o",
+      rebase_path(target_gen_dir, root_build_dir) +
+        "/{{source_name_part}}.h" ]
+  }
+```
+### <a name="func_bundle_data"></a>**bundle_data**: [iOS/macOS] Declare a target without output.
+
+```
+  This target type allows one to declare data that is required at runtime. It is
+  used to inform "create_bundle" targets of the files to copy into generated
+  bundle, see "gn help create_bundle" for help.
+
+  The target must define a list of files as "sources" and a single "outputs".
+  If there are multiple files, source expansions must be used to express the
+  output. The output must reference a file inside of {{bundle_root_dir}}.
+
+  This target can be used on all platforms though it is designed only to
+  generate iOS/macOS bundle. In cross-platform projects, it is advised to put it
+  behind iOS/macOS conditionals.
+
+  See "gn help create_bundle" for more information.
+```
+
+#### **Variables**
+
+```
+  sources*, outputs*, deps, data_deps, metadata, public_deps, visibility
+  * = required
+```
+
+#### **Examples**
+
+```
+  bundle_data("icudata") {
+    sources = [ "sources/data/in/icudtl.dat" ]
+    outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
+  }
+
+  bundle_data("base_unittests_bundle_data]") {
+    sources = [ "test/data" ]
+    outputs = [
+      "{{bundle_resources_dir}}/{{source_root_relative_dir}}/" +
+          "{{source_file_part}}"
+    ]
+  }
+
+  bundle_data("material_typography_bundle_data") {
+    sources = [
+      "src/MaterialTypography.bundle/Roboto-Bold.ttf",
+      "src/MaterialTypography.bundle/Roboto-Italic.ttf",
+      "src/MaterialTypography.bundle/Roboto-Regular.ttf",
+      "src/MaterialTypography.bundle/Roboto-Thin.ttf",
+    ]
+    outputs = [
+      "{{bundle_resources_dir}}/MaterialTypography.bundle/"
+          "{{source_file_part}}"
+    ]
+  }
+```
+### <a name="func_copy"></a>**copy**: Declare a target that copies files.
+
+#### **File name handling**
+
+```
+  All output files must be inside the output directory of the build. You would
+  generally use |$target_out_dir| or |$target_gen_dir| to reference the output
+  or generated intermediate file directories, respectively.
+
+  Both "sources" and "outputs" must be specified. Sources can include as many
+  files as you want, but there can only be one item in the outputs list (plural
+  is used for the name for consistency with other target types).
+
+  If there is more than one source file, your output name should specify a
+  mapping from each source file to an output file name using source expansion
+  (see "gn help source_expansion"). The placeholders will look like
+  "{{source_name_part}}", for example.
+```
+
+#### **Examples**
+
+```
+  # Write a rule that copies a checked-in DLL to the output directory.
+  copy("mydll") {
+    sources = [ "mydll.dll" ]
+    outputs = [ "$target_out_dir/mydll.dll" ]
+  }
+
+  # Write a rule to copy several files to the target generated files directory.
+  copy("myfiles") {
+    sources = [ "data1.dat", "data2.dat", "data3.dat" ]
+
+    # Use source expansion to generate output files with the corresponding file
+    # names in the gen dir. This will just copy each file.
+    outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+  }
+```
+### <a name="func_create_bundle"></a>**create_bundle**: [ios/macOS] Build an iOS or macOS bundle.
+
+```
+  This target generates an iOS or macOS bundle (which is a directory with a
+  well-know structure). This target does not define any sources, instead they
+  are computed from all "bundle_data" target this one depends on transitively
+  (the recursion stops at "create_bundle" targets).
+
+  The "bundle_*_dir" are be used for the expansion of {{bundle_*_dir}} rules in
+  "bundle_data" outputs. The properties are optional but must be defined if any
+  of the "bundle_data" target use them.
+
+  This target can be used on all platforms though it is designed only to
+  generate iOS or macOS bundle. In cross-platform projects, it is advised to put
+  it behind iOS/macOS conditionals.
+
+  If a create_bundle is specified as a data_deps for another target, the bundle
+  is considered a leaf, and its public and private dependencies will not
+  contribute to any data or data_deps. Required runtime dependencies should be
+  placed in the bundle. A create_bundle can declare its own explicit data and
+  data_deps, however.
+```
+
+#### **Code signing**
+
+```
+  Some bundle needs to be code signed as part of the build (on iOS all
+  application needs to be code signed to run on a device). The code signature
+  can be configured via the code_signing_script variable.
+
+  If set, code_signing_script is the path of a script that invoked after all
+  files have been moved into the bundle. The script must not change any file in
+  the bundle, but may add new files.
+
+  If code_signing_script is defined, then code_signing_outputs must also be
+  defined and non-empty to inform when the script needs to be re-run. The
+  code_signing_args will be passed as is to the script (so path have to be
+  rebased) and additional inputs may be listed with the variable
+  code_signing_sources.
+```
+
+#### **Variables**
+
+```
+  bundle_root_dir, bundle_contents_dir, bundle_resources_dir,
+  bundle_executable_dir, bundle_deps_filter, deps, data_deps, public_deps,
+  visibility, product_type, code_signing_args, code_signing_script,
+  code_signing_sources, code_signing_outputs, xcode_extra_attributes,
+  xcode_test_application_name, partial_info_plist, metadata
+```
+
+#### **Example**
+
+```
+  # Defines a template to create an application. On most platform, this is just
+  # an alias for an "executable" target, but on iOS/macOS, it builds an
+  # application bundle.
+  template("app") {
+    if (!is_ios && !is_mac) {
+      executable(target_name) {
+        forward_variables_from(invoker, "*")
+      }
+    } else {
+      app_name = target_name
+      gen_path = target_gen_dir
+
+      action("${app_name}_generate_info_plist") {
+        script = [ "//build/ios/ios_gen_plist.py" ]
+        sources = [ "templates/Info.plist" ]
+        outputs = [ "$gen_path/Info.plist" ]
+        args = rebase_path(sources, root_build_dir) +
+               rebase_path(outputs, root_build_dir)
+      }
+
+      bundle_data("${app_name}_bundle_info_plist") {
+        public_deps = [ ":${app_name}_generate_info_plist" ]
+        sources = [ "$gen_path/Info.plist" ]
+        outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
+      }
+
+      executable("${app_name}_generate_executable") {
+        forward_variables_from(invoker, "*", [
+                                               "output_name",
+                                               "visibility",
+                                             ])
+        output_name =
+            rebase_path("$gen_path/$app_name", root_build_dir)
+      }
+
+      code_signing =
+          defined(invoker.code_signing) && invoker.code_signing
+
+      if (!is_ios || !code_signing) {
+        bundle_data("${app_name}_bundle_executable") {
+          public_deps = [ ":${app_name}_generate_executable" ]
+          sources = [ "$gen_path/$app_name" ]
+          outputs = [ "{{bundle_executable_dir}}/$app_name" ]
+        }
+      }
+
+      create_bundle("$app_name.app") {
+        product_type = "com.apple.product-type.application"
+
+        if (is_ios) {
+          bundle_root_dir = "$root_build_dir/$target_name"
+          bundle_contents_dir = bundle_root_dir
+          bundle_resources_dir = bundle_contents_dir
+          bundle_executable_dir = bundle_contents_dir
+
+          xcode_extra_attributes = {
+            ONLY_ACTIVE_ARCH = "YES"
+            DEBUG_INFORMATION_FORMAT = "dwarf"
+          }
+        } else {
+          bundle_root_dir = "$root_build_dir/$target_name"
+          bundle_contents_dir  = "$bundle_root_dir/Contents"
+          bundle_resources_dir = "$bundle_contents_dir/Resources"
+          bundle_executable_dir = "$bundle_contents_dir/MacOS"
+        }
+        deps = [ ":${app_name}_bundle_info_plist" ]
+        if (is_ios && code_signing) {
+          deps += [ ":${app_name}_generate_executable" ]
+          code_signing_script = "//build/config/ios/codesign.py"
+          code_signing_sources = [
+            invoker.entitlements_path,
+            "$target_gen_dir/$app_name",
+          ]
+          code_signing_outputs = [
+            "$bundle_root_dir/$app_name",
+            "$bundle_root_dir/_CodeSignature/CodeResources",
+            "$bundle_root_dir/embedded.mobileprovision",
+            "$target_gen_dir/$app_name.xcent",
+          ]
+          code_signing_args = [
+            "-i=" + ios_code_signing_identity,
+            "-b=" + rebase_path(
+                "$target_gen_dir/$app_name", root_build_dir),
+            "-e=" + rebase_path(
+                invoker.entitlements_path, root_build_dir),
+            "-e=" + rebase_path(
+                "$target_gen_dir/$app_name.xcent", root_build_dir),
+            rebase_path(bundle_root_dir, root_build_dir),
+          ]
+        } else {
+          deps += [ ":${app_name}_bundle_executable" ]
+        }
+      }
+    }
+  }
+```
+### <a name="func_executable"></a>**executable**: Declare an executable target.
+
+#### **Language and compilation**
+
+```
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name
+```
+### <a name="func_generated_file"></a>**generated_file**: Declare a generated_file target.
+
+```
+  Writes data value(s) to disk on resolution. This target type mirrors some
+  functionality of the write_file() function, but also provides the ability to
+  collect metadata from its dependencies on resolution rather than writing out
+  at parse time.
+
+  The `outputs` variable is required to be a list with a single element,
+  specifying the intended location of the output file.
+
+  The `output_conversion` variable specified the format to write the
+  value. See `gn help output_conversion`.
+
+  One of `contents` or `data_keys` must be specified; use of `data` will write
+  the contents of that value to file, while use of `data_keys` will trigger a
+  metadata collection walk based on the dependencies of the target and the
+  optional values of the `rebase` and `walk_keys` variables. See
+  `gn help metadata`.
+
+  Collected metadata, if specified, will be returned in postorder of
+  dependencies. See the example for details.
+```
+
+#### **Example (metadata collection)**
+
+```
+  Given the following targets defined in //base/BUILD.gn, where A depends on B
+  and B depends on C and D:
+
+    group("a") {
+      metadata = {
+        doom_melon = [ "enable" ]
+        my_files = [ "foo.cpp" ]
+
+        # Note: this is functionally equivalent to not defining `my_barrier`
+        # at all in this target's metadata.
+        my_barrier = [ "" ]
+      }
+
+      deps = [ ":b" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "bar.cpp" ]
+        my_barrier = [ ":c" ]
+      }
+
+      deps = [ ":c", ":d" ]
+    }
+
+    group("c") {
+      metadata = {
+        doom_melon = [ "disable" ]
+        my_files = [ "baz.cpp" ]
+      }
+    }
+
+    group("d") {
+      metadata = {
+        my_files = [ "missing.cpp" ]
+      }
+    }
+
+  If the following generated_file target is defined:
+
+    generated_file("my_files_metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files" ]
+
+      deps = [ "//base:a" ]
+    }
+
+  The following will be written to "$root_build_dir/my_files.json" (less the
+  comments):
+    [
+      "baz.cpp",  // from //base:c via //base:b
+      "missing.cpp"  // from //base:d via //base:b
+      "bar.cpp",  // from //base:b via //base:a
+      "foo.cpp",  // from //base:a
+    ]
+
+  Alternatively, as an example of using walk_keys, if the following
+  generated_file target is defined:
+
+  generated_file("my_files_metadata") {
+    outputs = [ "$root_build_dir/my_files.json" ]
+    data_keys = [ "my_files" ]
+    walk_keys = [ "my_barrier" ]
+
+    deps = [ "//base:a" ]
+  }
+
+  The following will be written to "$root_build_dir/my_files.json" (again less
+  the comments):
+    [
+      "baz.cpp",  // from //base:c via //base:b
+      "bar.cpp",  // from //base:b via //base:a
+      "foo.cpp",  // from //base:a
+    ]
+
+  If `rebase` is used in the following generated_file target:
+
+  generated_file("my_files_metadata") {
+    outputs = [ "$root_build_dir/my_files.json" ]
+    data_keys = [ "my_files" ]
+    walk_keys = [ "my_barrier" ]
+    rebase = root_build_dir
+
+    deps = [ "//base:a" ]
+  }
+
+  The following will be written to "$root_build_dir/my_files.json" (again less
+  the comments) (assuming root_build_dir = "//out"):
+    [
+      "../base/baz.cpp",  // from //base:c via //base:b
+      "../base/bar.cpp",  // from //base:b via //base:a
+      "../base/foo.cpp",  // from //base:a
+    ]
+```
+
+#### **Variables**
+
+```
+  contents
+  data_keys
+  rebase
+  walk_keys
+  output_conversion
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+```
+### <a name="func_group"></a>**group**: Declare a named group of targets.
+
+```
+  This target type allows you to create meta-targets that just collect a set of
+  dependencies into one named target. Groups can additionally specify configs
+  that apply to their dependents.
+```
+
+#### **Variables**
+
+```
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+```
+
+#### **Example**
+
+```
+  group("all") {
+    deps = [
+      "//project:runner",
+      "//project:unit_tests",
+    ]
+  }
+```
+### <a name="func_loadable_module"></a>**loadable_module**: Declare a loadable module target.
+
+```
+  This target type allows you to create an object file that is (and can only
+  be) loaded and unloaded at runtime.
+
+  A loadable module will be specified on the linker line for targets listing
+  the loadable module in its "deps". If you don't want this (if you don't need
+  to dynamically load the library at runtime), then you should use a
+  "shared_library" target type instead.
+```
+
+#### **Language and compilation**
+
+```
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name, crate_type
+```
+### <a name="func_rust_library"></a>**rust_library**: Declare a Rust library target.
+
+```
+  A Rust library is an archive containing additional rust-c provided metadata.
+  These are the files produced by the rustc compiler with the `.rlib`
+  extension, and are the intermediate step for most Rust-based binaries.
+```
+
+#### **Language and compilation**
+
+```
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name
+```
+### <a name="func_rust_proc_macro"></a>**rust_proc_macro**: Declare a Rust procedural macro target.
+
+```
+  A Rust procedural macro allows creating syntax extensions as execution of a
+  function. They are compiled as dynamic libraries and used by the compiler at
+  runtime.
+
+  Their use is the same as of other Rust libraries, but their build has some
+  additional restrictions in terms of supported flags.
+```
+
+#### **Language and compilation**
+
+```
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name
+```
+### <a name="func_shared_library"></a>**shared_library**: Declare a shared library target.
+
+```
+  A shared library will be specified on the linker line for targets listing the
+  shared library in its "deps". If you don't want this (say you dynamically
+  load the library at runtime), then you should depend on the shared library
+  via "data_deps" or, on Darwin platforms, use a "loadable_module" target type
+  instead.
+```
+
+#### **Language and compilation**
+
+```
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name, crate_type
+```
+### <a name="func_source_set"></a>**source_set**: Declare a source set target.
+
+```
+  Only C-language source sets are supported at the moment.
+```
+
+#### **C-language source_sets**
+
+```
+  A source set is a collection of sources that get compiled, but are not linked
+  to produce any kind of library. Instead, the resulting object files are
+  implicitly added to the linker line of all targets that depend on the source
+  set.
+
+  In most cases, a source set will behave like a static library, except no
+  actual library file will be produced. This will make the build go a little
+  faster by skipping creation of a large static library, while maintaining the
+  organizational benefits of focused build targets.
+
+  The main difference between a source set and a static library is around
+  handling of exported symbols. Most linkers assume declaring a function
+  exported means exported from the static library. The linker can then do dead
+  code elimination to delete code not reachable from exported functions.
+
+  A source set will not do this code elimination since there is no link step.
+  This allows you to link many source sets into a shared library and have the
+  "exported symbol" notation indicate "export from the final shared library and
+  not from the intermediate targets." There is no way to express this concept
+  when linking multiple static libraries into a shared library.
+```
+
+#### **Variables**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+```
+### <a name="func_static_library"></a>**static_library**: Declare a static library target.
+
+```
+  Make a ".a" / ".lib" file.
+
+  If you only need the static library for intermediate results in the build,
+  you should consider a source_set instead since it will skip the (potentially
+  slow) step of creating the intermediate library file.
+```
+
+#### **Variables**
+
+```
+  complete_static_lib
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Deps: data_deps, deps, public_deps
+  Dependent configs: all_dependent_configs, public_configs
+  General: check_includes, configs, data, friend, inputs, metadata,
+           output_name, output_extension, public, sources, testonly,
+           visibility
+  Rust variables: aliased_deps, crate_root, crate_name
+
+  The tools and commands used to create this target type will be
+  determined by the source files in its sources. Targets containing
+  multiple compiler-incompatible languages are not allowed (e.g. a
+  target containing both C and C++ sources is acceptable, but a
+  target containing C and Rust sources is not).
+```
+### <a name="func_target"></a>**target**: Declare an target with the given programmatic type.
+
+```
+  target(target_type_string, target_name_string) { ... }
+
+  The target() function is a way to invoke a built-in target or template with a
+  type determined at runtime. This is useful for cases where the type of a
+  target might not be known statically.
+
+  Only templates and built-in target functions are supported for the
+  target_type_string parameter. Arbitrary functions, configs, and toolchains
+  are not supported.
+
+  The call:
+    target("source_set", "doom_melon") {
+  Is equivalent to:
+    source_set("doom_melon") {
+```
+
+#### **Example**
+
+```
+  if (foo_build_as_shared) {
+    my_type = "shared_library"
+  } else {
+    my_type = "source_set"
+  }
+
+  target(my_type, "foo") {
+    ...
+  }
+```
+## <a name="functions"></a>Buildfile functions
+
+### <a name="func_assert"></a>**assert**: Assert an expression is true at generation time.
+
+```
+  assert(<condition> [, <error string>])
+
+  If the condition is false, the build will fail with an error. If the
+  optional second argument is provided, that string will be printed
+  with the error message.
+```
+
+#### **Examples**
+
+```
+  assert(is_win)
+  assert(defined(sources), "Sources must be defined");
+```
+### <a name="func_config"></a>**config**: Defines a configuration object.
+
+```
+  Configuration objects can be applied to targets and specify sets of compiler
+  flags, includes, defines, etc. They provide a way to conveniently group sets
+  of this configuration information.
+
+  A config is referenced by its label just like a target.
+
+  The values in a config are additive only. If you want to remove a flag you
+  need to remove the corresponding config that sets it. The final set of flags,
+  defines, etc. for a target is generated in this order:
+
+   1. The values specified directly on the target (rather than using a config.
+   2. The configs specified in the target's "configs" list, in order.
+   3. Public_configs from a breadth-first traversal of the dependency tree in
+      the order that the targets appear in "deps".
+   4. All dependent configs from a breadth-first traversal of the dependency
+      tree in the order that the targets appear in "deps".
+```
+
+#### **More background**
+
+```
+  Configs solve a problem where the build system needs to have a higher-level
+  understanding of various compiler settings. For example, some compiler flags
+  have to appear in a certain order relative to each other, some settings like
+  defines and flags logically go together, and the build system needs to
+  de-duplicate flags even though raw command-line parameters can't always be
+  operated on in that way.
+
+  The config gives a name to a group of settings that can then be reasoned
+  about by GN. GN can know that configs with the same label are the same thing
+  so can be de-duplicated. It allows related settings to be grouped so they
+  are added or removed as a unit. And it allows targets to refer to settings
+  with conceptual names ("no_rtti", "enable_exceptions", etc.) rather than
+  having to hard-coding every compiler's flags each time they are referred to.
+```
+
+#### **Variables valid in a config definition**
+
+```
+  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,
+         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,
+         libs, precompiled_header, precompiled_source, rustflags,
+         rustenv, swiftflags
+  Nested configs: configs
+```
+
+#### **Variables on a target used to apply configs**
+
+```
+  all_dependent_configs, configs, public_configs
+```
+
+#### **Example**
+
+```
+  config("myconfig") {
+    include_dirs = [ "include/common" ]
+    defines = [ "ENABLE_DOOM_MELON" ]
+  }
+
+  executable("mything") {
+    configs = [ ":myconfig" ]
+  }
+```
+### <a name="func_declare_args"></a>**declare_args**: Declare build arguments.
+
+```
+  Introduces the given arguments into the current scope. If they are not
+  specified on the command line or in a toolchain's arguments, the default
+  values given in the declare_args block will be used. However, these defaults
+  will not override command-line values.
+
+  See also "gn help buildargs" for an overview.
+
+  The precise behavior of declare args is:
+
+   1. The declare_args() block executes. Any variable defined in the enclosing
+      scope is available for reading, but any variable defined earlier in
+      the current scope is not (since the overrides haven't been applied yet).
+
+   2. At the end of executing the block, any variables set within that scope
+      are saved, with the values specified in the block used as the "default value"
+      for that argument. Once saved, these variables are available for override
+      via args.gn.
+
+   3. User-defined overrides are applied. Anything set in "gn args" now
+      overrides any default values. The resulting set of variables is promoted
+      to be readable from the following code in the file.
+
+  This has some ramifications that may not be obvious:
+
+    - You should not perform difficult work inside a declare_args block since
+      this only sets a default value that may be discarded. In particular,
+      don't use the result of exec_script() to set the default value. If you
+      want to have a script-defined default, set some default "undefined" value
+      like [], "", or -1, and after the declare_args block, call exec_script if
+      the value is unset by the user.
+
+    - Because you cannot read the value of a variable defined in the same
+      block, if you need to make the default value of one arg depend
+      on the possibly-overridden value of another, write two separate
+      declare_args() blocks:
+
+        declare_args() {
+          enable_foo = true
+        }
+        declare_args() {
+          # Bar defaults to same user-overridden state as foo.
+          enable_bar = enable_foo
+        }
+```
+
+#### **Example**
+
+```
+  declare_args() {
+    enable_teleporter = true
+    enable_doom_melon = false
+  }
+
+  If you want to override the (default disabled) Doom Melon:
+    gn --args="enable_doom_melon=true enable_teleporter=true"
+  This also sets the teleporter, but it's already defaulted to on so it will
+  have no effect.
+```
+### <a name="func_defined"></a>**defined**: Returns whether an identifier is defined.
+
+```
+  Returns true if the given argument is defined. This is most useful in
+  templates to assert that the caller set things up properly.
+
+  You can pass an identifier:
+    defined(foo)
+  which will return true or false depending on whether foo is defined in the
+  current scope.
+
+  You can also check a named scope:
+    defined(foo.bar)
+  which will return true or false depending on whether bar is defined in the
+  named scope foo. It will throw an error if foo is not defined or is not a
+  scope.
+```
+
+#### **Example**
+
+```
+  template("mytemplate") {
+    # To help users call this template properly...
+    assert(defined(invoker.sources), "Sources must be defined")
+
+    # If we want to accept an optional "values" argument, we don't
+    # want to dereference something that may not be defined.
+    if (defined(invoker.values)) {
+      values = invoker.values
+    } else {
+      values = "some default value"
+    }
+  }
+```
+### <a name="func_exec_script"></a>**exec_script**: Synchronously run a script and return the output.
+
+```
+  exec_script(filename,
+              arguments = [],
+              input_conversion = "",
+              file_dependencies = [])
+
+  Runs the given script, returning the stdout of the script. The build
+  generation will fail if the script does not exist or returns a nonzero exit
+  code.
+
+  The current directory when executing the script will be the root build
+  directory. If you are passing file names, you will want to use the
+  rebase_path() function to make file names relative to this path (see "gn help
+  rebase_path").
+
+  The default script interpreter is Python ("python" on POSIX, "python.exe" or
+  "python.bat" on Windows). This can be configured by the script_executable
+  variable, see "gn help dotfile".
+```
+
+#### **Arguments**:
+
+```
+  filename:
+      File name of script to execute. Non-absolute names will be treated as
+      relative to the current build file.
+
+  arguments:
+      A list of strings to be passed to the script as arguments. May be
+      unspecified or the empty list which means no arguments.
+
+  input_conversion:
+      Controls how the file is read and parsed. See "gn help io_conversion".
+
+      If unspecified, defaults to the empty string which causes the script
+      result to be discarded. exec script will return None.
+
+  dependencies:
+      (Optional) A list of files that this script reads or otherwise depends
+      on. These dependencies will be added to the build result such that if any
+      of them change, the build will be regenerated and the script will be
+      re-run.
+
+      The script itself will be an implicit dependency so you do not need to
+      list it.
+```
+
+#### **Example**
+
+```
+  all_lines = exec_script(
+      "myscript.py", [some_input], "list lines",
+      [ rebase_path("data_file.txt", root_build_dir) ])
+
+  # This example just calls the script with no arguments and discards the
+  # result.
+  exec_script("//foo/bar/myscript.py")
+```
+### <a name="func_filter_exclude"></a>**filter_exclude**: Remove values that match a set of patterns.
+
+```
+  filter_exclude(values, exclude_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument exclude_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Any elements in values matching at least one
+  of those patterns will be excluded.
+```
+
+#### **Examples**
+```
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_exclude(values, [ "*.proto" ])
+  # result will be [ "foo.cc", "foo.h" ]
+```
+### <a name="func_filter_include"></a>**filter_include**: Remove values that do not match a set of patterns.
+
+```
+  filter_include(values, include_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument include_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Only elements from values matching at least
+  one of the pattern will be included.
+```
+
+#### **Examples**
+```
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_include(values, [ "*.proto" ])
+  # result will be [ "foo.proto" ]
+```
+### <a name="func_foreach"></a>**foreach**: Iterate over a list.
+
+```
+    foreach(<loop_var>, <list>) {
+      <loop contents>
+    }
+
+  Executes the loop contents block over each item in the list, assigning the
+  loop_var to each item in sequence. The <loop_var> will be a copy so assigning
+  to it will not mutate the list. The loop will iterate over a copy of <list>
+  so mutating it inside the loop will not affect iteration.
+
+  The block does not introduce a new scope, so that variable assignments inside
+  the loop will be visible once the loop terminates.
+
+  The loop variable will temporarily shadow any existing variables with the
+  same name for the duration of the loop. After the loop terminates the loop
+  variable will no longer be in scope, and the previous value (if any) will be
+  restored.
+```
+
+#### **Example**
+
+```
+  mylist = [ "a", "b", "c" ]
+  foreach(i, mylist) {
+    print(i)
+  }
+
+  Prints:
+  a
+  b
+  c
+```
+### <a name="func_forward_variables_from"></a>**forward_variables_from**: Copies variables from a different scope.
+
+```
+  forward_variables_from(from_scope, variable_list_or_star,
+                         variable_to_not_forward_list = [])
+
+  Copies the given variables from the given scope to the local scope if they
+  exist. This is normally used in the context of templates to use the values of
+  variables defined in the template invocation to a template-defined target.
+
+  The variables in the given variable_list will be copied if they exist in the
+  given scope or any enclosing scope. If they do not exist, nothing will happen
+  and they be left undefined in the current scope.
+
+  As a special case, if the variable_list is a string with the value of "*",
+  all variables from the given scope will be copied. "*" only copies variables
+  set directly on the from_scope, not enclosing ones. Otherwise it would
+  duplicate all global variables.
+
+  When an explicit list of variables is supplied, if the variable exists in the
+  current (destination) scope already, an error will be thrown. If "*" is
+  specified, variables in the current scope will be clobbered (the latter is
+  important because most targets have an implicit configs list, which means it
+  wouldn't work at all if it didn't clobber).
+
+  If variables_to_not_forward_list is non-empty, then it must contains a list
+  of variable names that will not be forwarded. This is mostly useful when
+  variable_list_or_star has a value of "*".
+```
+
+#### **Examples**
+
+```
+  # forward_variables_from(invoker, ["foo"])
+  # is equivalent to:
+  assert(!defined(foo))
+  if (defined(invoker.foo)) {
+    foo = invoker.foo
+  }
+
+  # This is a common action template. It would invoke a script with some given
+  # parameters, and wants to use the various types of deps and the visibility
+  # from the invoker if it's defined. It also injects an additional dependency
+  # to all targets.
+  template("my_test") {
+    action(target_name) {
+      forward_variables_from(invoker, [ "data_deps", "deps",
+                                        "public_deps", "visibility"])
+      # Add our test code to the dependencies.
+      # "deps" may or may not be defined at this point.
+      if (defined(deps)) {
+        deps += [ "//tools/doom_melon" ]
+      } else {
+        deps = [ "//tools/doom_melon" ]
+      }
+    }
+  }
+
+  # This is a template around a target whose type depends on a global variable.
+  # It forwards all values from the invoker.
+  template("my_wrapper") {
+    target(my_wrapper_target_type, target_name) {
+      forward_variables_from(invoker, "*")
+    }
+  }
+
+  # A template that wraps another. It adds behavior based on one
+  # variable, and forwards all others to the nested target.
+  template("my_ios_test_app") {
+    ios_test_app(target_name) {
+      forward_variables_from(invoker, "*", ["test_bundle_name"])
+      if (!defined(extra_substitutions)) {
+        extra_substitutions = []
+      }
+      extra_substitutions += [ "BUNDLE_ID_TEST_NAME=$test_bundle_name" ]
+    }
+  }
+```
+### <a name="func_get_label_info"></a>**get_label_info**: Get an attribute from a target's label.
+
+```
+  get_label_info(target_label, what)
+
+  Given the label of a target, returns some attribute of that target. The
+  target need not have been previously defined in the same file, since none of
+  the attributes depend on the actual target definition, only the label itself.
+
+  See also "gn help get_target_outputs".
+```
+
+#### **Possible values for the "what" parameter**
+
+```
+  "name"
+      The short name of the target. This will match the value of the
+      "target_name" variable inside that target's declaration. For the label
+      "//foo/bar:baz" this will return "baz".
+
+  "dir"
+      The directory containing the target's definition, with no slash at the
+      end. For the label "//foo/bar:baz" this will return "//foo/bar".
+
+  "target_gen_dir"
+      The generated file directory for the target. This will match the value of
+      the "target_gen_dir" variable when inside that target's declaration.
+
+  "root_gen_dir"
+      The root of the generated file tree for the target. This will match the
+      value of the "root_gen_dir" variable when inside that target's
+      declaration.
+
+  "target_out_dir
+      The output directory for the target. This will match the value of the
+      "target_out_dir" variable when inside that target's declaration.
+
+  "root_out_dir"
+      The root of the output file tree for the target. This will match the
+      value of the "root_out_dir" variable when inside that target's
+      declaration.
+
+  "label_no_toolchain"
+      The fully qualified version of this label, not including the toolchain.
+      For the input ":bar" it might return "//foo:bar".
+
+  "label_with_toolchain"
+      The fully qualified version of this label, including the toolchain. For
+      the input ":bar" it might return "//foo:bar(//toolchain:x64)".
+
+  "toolchain"
+      The label of the toolchain. This will match the value of the
+      "current_toolchain" variable when inside that target's declaration.
+```
+
+#### **Examples**
+
+```
+  get_label_info(":foo", "name")
+  # Returns string "foo".
+
+  get_label_info("//foo/bar:baz", "target_gen_dir")
+  # Returns string "//out/Debug/gen/foo/bar".
+```
+### <a name="func_get_path_info"></a>**get_path_info**: Extract parts of a file or directory name.
+
+```
+  get_path_info(input, what)
+
+  The first argument is either a string representing a file or directory name,
+  or a list of such strings. If the input is a list the return value will be a
+  list containing the result of applying the rule to each item in the input.
+```
+
+#### **Possible values for the "what" parameter**
+
+```
+  "file"
+      The substring after the last slash in the path, including the name and
+      extension. If the input ends in a slash, the empty string will be
+      returned.
+        "foo/bar.txt" => "bar.txt"
+        "bar.txt" => "bar.txt"
+        "foo/" => ""
+        "" => ""
+
+  "name"
+     The substring of the file name not including the extension.
+        "foo/bar.txt" => "bar"
+        "foo/bar" => "bar"
+        "foo/" => ""
+
+  "extension"
+      The substring following the last period following the last slash, or the
+      empty string if not found. The period is not included.
+        "foo/bar.txt" => "txt"
+        "foo/bar" => ""
+
+  "dir"
+      The directory portion of the name, not including the slash.
+        "foo/bar.txt" => "foo"
+        "//foo/bar" => "//foo"
+        "foo" => "."
+
+      The result will never end in a slash, so if the resulting is empty, the
+      system ("/") or source ("//") roots, a "." will be appended such that it
+      is always legal to append a slash and a filename and get a valid path.
+
+  "out_dir"
+      The output file directory corresponding to the path of the given file,
+      not including a trailing slash.
+        "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar"
+
+  "gen_dir"
+      The generated file directory corresponding to the path of the given file,
+      not including a trailing slash.
+        "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar"
+
+  "abspath"
+      The full absolute path name to the file or directory. It will be resolved
+      relative to the current directory, and then the source- absolute version
+      will be returned. If the input is system- absolute, the same input will
+      be returned.
+        "foo/bar.txt" => "//mydir/foo/bar.txt"
+        "foo/" => "//mydir/foo/"
+        "//foo/bar" => "//foo/bar"  (already absolute)
+        "/usr/include" => "/usr/include"  (already absolute)
+
+      If you want to make the path relative to another directory, or to be
+      system-absolute, see rebase_path().
+```
+
+#### **Examples**
+```
+  sources = [ "foo.cc", "foo.h" ]
+  result = get_path_info(source, "abspath")
+  # result will be [ "//mydir/foo.cc", "//mydir/foo.h" ]
+
+  result = get_path_info("//foo/bar/baz.cc", "dir")
+  # result will be "//foo/bar"
+
+  # Extract the source-absolute directory name,
+  result = get_path_info(get_path_info(path, "dir"), "abspath")
+```
+### <a name="func_get_target_outputs"></a>**get_target_outputs**: [file list] Get the list of outputs from a target.
+
+```
+  get_target_outputs(target_label)
+
+  Returns a list of output files for the named target. The named target must
+  have been previously defined in the current file before this function is
+  called (it can't reference targets in other files because there isn't a
+  defined execution order, and it obviously can't reference targets that are
+  defined after the function call).
+
+  Only copy, generated_file, and action targets are supported. The outputs from
+  binary targets will depend on the toolchain definition which won't
+  necessarily have been loaded by the time a given line of code has run, and
+  source sets and groups have no useful output file.
+```
+
+#### **Return value**
+
+```
+  The names in the resulting list will be absolute file paths (normally like
+  "//out/Debug/bar.exe", depending on the build directory).
+
+  action, copy, and generated_file targets: this will just return the files
+  specified in the "outputs" variable of the target.
+
+  action_foreach targets: this will return the result of applying the output
+  template to the sources (see "gn help source_expansion"). This will be the
+  same result (though with guaranteed absolute file paths), as
+  process_file_template will return for those inputs (see "gn help
+  process_file_template").
+
+  source sets and groups: this will return a list containing the path of the
+  "stamp" file that Ninja will produce once all outputs are generated. This
+  probably isn't very useful.
+```
+
+#### **Example**
+
+```
+  # Say this action generates a bunch of C source files.
+  action_foreach("my_action") {
+    sources = [ ... ]
+    outputs = [ ... ]
+  }
+
+  # Compile the resulting source files into a source set.
+  source_set("my_lib") {
+    sources = get_target_outputs(":my_action")
+  }
+```
+### <a name="func_getenv"></a>**getenv**: Get an environment variable.
+
+```
+  value = getenv(env_var_name)
+
+  Returns the value of the given environment variable. If the value is not
+  found, it will try to look up the variable with the "opposite" case (based on
+  the case of the first letter of the variable), but is otherwise
+  case-sensitive.
+
+  If the environment variable is not found, the empty string will be returned.
+  Note: it might be nice to extend this if we had the concept of "none" in the
+  language to indicate lookup failure.
+```
+
+#### **Example**
+
+```
+  home_dir = getenv("HOME")
+```
+### <a name="func_import"></a>**import**: Import a file into the current scope.
+
+```
+  The import command loads the rules and variables resulting from executing the
+  given file into the current scope.
+
+  By convention, imported files are named with a .gni extension.
+
+  An import is different than a C++ "include". The imported file is executed in
+  a standalone environment from the caller of the import command. The results
+  of this execution are cached for other files that import the same .gni file.
+
+  Note that you can not import a BUILD.gn file that's otherwise used in the
+  build. Files must either be imported or implicitly loaded as a result of deps
+  rules, but not both.
+
+  The imported file's scope will be merged with the scope at the point import
+  was called. If there is a conflict (both the current scope and the imported
+  file define some variable or rule with the same name but different value), a
+  runtime error will be thrown. Therefore, it's good practice to minimize the
+  stuff that an imported file defines.
+
+  Variables and templates beginning with an underscore '_' are considered
+  private and will not be imported. Imported files can use such variables for
+  internal computation without affecting other files.
+```
+
+#### **Examples**
+
+```
+  import("//build/rules/idl_compilation_rule.gni")
+
+  # Looks in the current directory.
+  import("my_vars.gni")
+```
+### <a name="func_not_needed"></a>**not_needed**: Mark variables from scope as not needed.
+
+```
+  not_needed(variable_list_or_star, variable_to_ignore_list = [])
+  not_needed(from_scope, variable_list_or_star,
+             variable_to_ignore_list = [])
+
+  Mark the variables in the current or given scope as not needed, which means
+  you will not get an error about unused variables for these. The
+  variable_to_ignore_list allows excluding variables from "all matches" if
+  variable_list_or_star is "*".
+```
+
+#### **Example**
+
+```
+  not_needed("*", [ "config" ])
+  not_needed([ "data_deps", "deps" ])
+  not_needed(invoker, "*", [ "config" ])
+  not_needed(invoker, [ "data_deps", "deps" ])
+```
+### <a name="func_pool"></a>**pool**: Defines a pool object.
+
+```
+  Pool objects can be applied to a tool to limit the parallelism of the
+  build. This object has a single property "depth" corresponding to
+  the number of tasks that may run simultaneously.
+
+  As the file containing the pool definition may be executed in the
+  context of more than one toolchain it is recommended to specify an
+  explicit toolchain when defining and referencing a pool.
+
+  A pool named "console" defined in the root build file represents Ninja's
+  console pool. Targets using this pool will have access to the console's
+  stdin and stdout, and output will not be buffered. This special pool must
+  have a depth of 1. Pools not defined in the root must not be named "console".
+  The console pool can only be defined for the default toolchain.
+  Refer to the Ninja documentation on the console pool for more info.
+
+  A pool is referenced by its label just like a target.
+```
+
+#### **Variables**
+
+```
+  depth*
+  * = required
+```
+
+#### **Example**
+
+```
+  if (current_toolchain == default_toolchain) {
+    pool("link_pool") {
+      depth = 1
+    }
+  }
+
+  toolchain("toolchain") {
+    tool("link") {
+      command = "..."
+      pool = ":link_pool($default_toolchain)"
+    }
+  }
+```
+### <a name="func_print"></a>**print**: Prints to the console.
+
+```
+  Prints all arguments to the console separated by spaces. A newline is
+  automatically appended to the end.
+
+  This function is intended for debugging. Note that build files are run in
+  parallel so you may get interleaved prints. A buildfile may also be executed
+  more than once in parallel in the context of different toolchains so the
+  prints from one file may be duplicated or
+  interleaved with itself.
+```
+
+#### **Examples**
+
+```
+  print("Hello world")
+
+  print(sources, deps)
+```
+### <a name="func_process_file_template"></a>**process_file_template**: Do template expansion over a list of files.
+
+```
+  process_file_template(source_list, template)
+
+  process_file_template applies a template list to a source file list,
+  returning the result of applying each template to each source. This is
+  typically used for computing output file names from input files.
+
+  In most cases, get_target_outputs() will give the same result with shorter,
+  more maintainable code. This function should only be used when that function
+  can't be used (like there's no target or the target is defined in another
+  build file).
+```
+
+#### **Arguments**
+
+```
+  The source_list is a list of file names.
+
+  The template can be a string or a list. If it is a list, multiple output
+  strings are generated for each input.
+
+  The template should contain source expansions to which each name in the
+  source list is applied. See "gn help source_expansion".
+```
+
+#### **Example**
+
+```
+  sources = [
+    "foo.idl",
+    "bar.idl",
+  ]
+  myoutputs = process_file_template(
+      sources,
+      [ "$target_gen_dir/{{source_name_part}}.cc",
+        "$target_gen_dir/{{source_name_part}}.h" ])
+
+ The result in this case will be:
+    [ "//out/Debug/foo.cc"
+      "//out/Debug/foo.h"
+      "//out/Debug/bar.cc"
+      "//out/Debug/bar.h" ]
+```
+### <a name="func_read_file"></a>**read_file**: Read a file into a variable.
+
+```
+  read_file(filename, input_conversion)
+
+  Whitespace will be trimmed from the end of the file. Throws an error if the
+  file can not be opened.
+```
+
+#### **Arguments**
+
+```
+  filename
+      Filename to read, relative to the build file.
+
+  input_conversion
+      Controls how the file is read and parsed. See "gn help io_conversion".
+```
+
+#### **Example**
+
+```
+  lines = read_file("foo.txt", "list lines")
+```
+### <a name="func_rebase_path"></a>**rebase_path**: Rebase a file or directory to another location.
+
+```
+  converted = rebase_path(input,
+                          new_base = "",
+                          current_base = ".")
+
+  Takes a string argument representing a file name, or a list of such strings
+  and converts it/them to be relative to a different base directory.
+
+  When invoking the compiler or scripts, GN will automatically convert sources
+  and include directories to be relative to the build directory. However, if
+  you're passing files directly in the "args" array or doing other manual
+  manipulations where GN doesn't know something is a file name, you will need
+  to convert paths to be relative to what your tool is expecting.
+
+  The common case is to use this to convert paths relative to the current
+  directory to be relative to the build directory (which will be the current
+  directory when executing scripts).
+
+  If you want to convert a file path to be source-absolute (that is, beginning
+  with a double slash like "//foo/bar"), you should use the get_path_info()
+  function. This function won't work because it will always make relative
+  paths, and it needs to support making paths relative to the source root, so
+  it can't also generate source-absolute paths without more special-cases.
+```
+
+#### **Arguments**
+
+```
+  input
+      A string or list of strings representing file or directory names. These
+      can be relative paths ("foo/bar.txt"), system absolute paths
+      ("/foo/bar.txt"), or source absolute paths ("//foo/bar.txt").
+
+  new_base
+      The directory to convert the paths to be relative to. This can be an
+      absolute path or a relative path (which will be treated as being relative
+      to the current BUILD-file's directory).
+
+      As a special case, if new_base is the empty string (the default), all
+      paths will be converted to system-absolute native style paths with system
+      path separators. This is useful for invoking external programs.
+
+  current_base
+      Directory representing the base for relative paths in the input. If this
+      is not an absolute path, it will be treated as being relative to the
+      current build file. Use "." (the default) to convert paths from the
+      current BUILD-file's directory.
+```
+
+#### **Return value**
+
+```
+  The return value will be the same type as the input value (either a string or
+  a list of strings). All relative and source-absolute file names will be
+  converted to be relative to the requested output System-absolute paths will
+  be unchanged.
+
+  Whether an output path will end in a slash will match whether the
+  corresponding input path ends in a slash. It will return "." or "./"
+  (depending on whether the input ends in a slash) to avoid returning empty
+  strings. This means if you want a root path ("//" or "/") not ending in a
+  slash, you can add a dot ("//.").
+```
+
+#### **Example**
+
+```
+  # Convert a file in the current directory to be relative to the build
+  # directory (the current dir when executing compilers and scripts).
+  foo = rebase_path("myfile.txt", root_build_dir)
+  # might produce "../../project/myfile.txt".
+
+  # Convert a file to be system absolute:
+  foo = rebase_path("myfile.txt")
+  # Might produce "D:\\source\\project\\myfile.txt" on Windows or
+  # "/home/you/source/project/myfile.txt" on Linux.
+
+  # Typical usage for converting to the build directory for a script.
+  action("myscript") {
+    # Don't convert sources, GN will automatically convert these to be relative
+    # to the build directory when it constructs the command line for your
+    # script.
+    sources = [ "foo.txt", "bar.txt" ]
+
+    # Extra file args passed manually need to be explicitly converted
+    # to be relative to the build directory:
+    args = [
+      "--data",
+      rebase_path("//mything/data/input.dat", root_build_dir),
+      "--rel",
+      rebase_path("relative_path.txt", root_build_dir)
+    ] + rebase_path(sources, root_build_dir)
+  }
+```
+### <a name="func_set_default_toolchain"></a>**set_default_toolchain**: Sets the default toolchain name.
+
+```
+  set_default_toolchain(toolchain_label)
+
+  The given label should identify a toolchain definition (see "gn help
+  toolchain"). This toolchain will be used for all targets unless otherwise
+  specified.
+
+  This function is only valid to call during the processing of the build
+  configuration file. Since the build configuration file is processed
+  separately for each toolchain, this function will be a no-op when called
+  under any non-default toolchains.
+
+  For example, the default toolchain should be appropriate for the current
+  environment. If the current environment is 32-bit and somebody references a
+  target with a 64-bit toolchain, we wouldn't want processing of the build
+  config file for the 64-bit toolchain to reset the default toolchain to
+  64-bit, we want to keep it 32-bits.
+```
+
+#### **Argument**
+
+```
+  toolchain_label
+      Toolchain name.
+```
+
+#### **Example**
+
+```
+  # Set default toolchain only has an effect when run in the context of the
+  # default toolchain. Pick the right one according to the current CPU
+  # architecture.
+  if (target_cpu == "x64") {
+    set_default_toolchain("//toolchains:64")
+  } else if (target_cpu == "x86") {
+    set_default_toolchain("//toolchains:32")
+  }
+```
+### <a name="func_set_defaults"></a>**set_defaults**: Set default values for a target type.
+
+```
+  set_defaults(<target_type_name>) { <values...> }
+
+  Sets the default values for a given target type. Whenever target_type_name is
+  seen in the future, the values specified in set_default's block will be
+  copied into the current scope.
+
+  When the target type is used, the variable copying is very strict. If a
+  variable with that name is already in scope, the build will fail with an
+  error.
+
+  set_defaults can be used for built-in target types ("executable",
+  "shared_library", etc.) and custom ones defined via the "template" command.
+  It can be called more than once and the most recent call in any scope will
+  apply, but there is no way to refer to the previous defaults and modify them
+  (each call to set_defaults must supply a complete list of all defaults it
+  wants). If you want to share defaults, store them in a separate variable.
+```
+
+#### **Example**
+
+```
+  set_defaults("static_library") {
+    configs = [ "//tools/mything:settings" ]
+  }
+
+  static_library("mylib") {
+    # The configs will be auto-populated as above. You can remove it if
+    # you don't want the default for a particular default:
+    configs -= [ "//tools/mything:settings" ]
+  }
+```
+### <a name="func_split_list"></a>**split_list**: Splits a list into N different sub-lists.
+
+```
+  result = split_list(input, n)
+
+  Given a list and a number N, splits the list into N sub-lists of
+  approximately equal size. The return value is a list of the sub-lists. The
+  result will always be a list of size N. If N is greater than the number of
+  elements in the input, it will be padded with empty lists.
+
+  The expected use is to divide source files into smaller uniform chunks.
+```
+
+#### **Example**
+
+```
+  The code:
+    mylist = [1, 2, 3, 4, 5, 6]
+    print(split_list(mylist, 3))
+
+  Will print:
+    [[1, 2], [3, 4], [5, 6]
+```
+### <a name="func_string_join"></a>**string_join**: Concatenates a list of strings with a separator.
+
+```
+  result = string_join(separator, strings)
+
+  Concatenate a list of strings with intervening occurrences of separator.
+```
+
+#### **Examples**
+
+```
+    string_join("", ["a", "b", "c"])    --> "abc"
+    string_join("|", ["a", "b", "c"])   --> "a|b|c"
+    string_join(", ", ["a", "b", "c"])  --> "a, b, c"
+    string_join("s", ["", ""])          --> "s"
+```
+### <a name="func_string_replace"></a>**string_replace**: Replaces substring in the given string.
+
+```
+  result = string_replace(str, old, new[, max])
+
+  Returns a copy of the string str in which the occurrences of old have been
+  replaced with new, optionally restricting the number of replacements. The
+  replacement is performed sequentially, so if new contains old, it won't be
+  replaced.
+```
+
+#### **Example**
+
+```
+  The code:
+    mystr = "Hello, world!"
+    print(string_replace(mystr, "world", "GN"))
+
+  Will print:
+    Hello, GN!
+```
+### <a name="func_string_split"></a>**string_split**: Split string into a list of strings.
+
+```
+  result = string_split(str[, sep])
+
+  Split string into all substrings separated by separator and returns a list
+  of the substrings between those separators.
+
+  If the separator argument is omitted, the split is by any whitespace, and
+  any leading/trailing whitespace is ignored; similar to Python's str.split().
+```
+
+#### **Examples without a separator (split on whitespace)**:
+
+```
+  string_split("")          --> []
+  string_split("a")         --> ["a"]
+  string_split(" aa  bb")   --> ["aa", "bb"]
+```
+
+#### **Examples with a separator (split on separators)**:
+
+```
+  string_split("", "|")           --> [""]
+  string_split("  a b  ", " ")    --> ["", "", "a", "b", "", ""]
+  string_split("aa+-bb+-c", "+-") --> ["aa", "bb", "c"]
+```
+### <a name="func_template"></a>**template**: Define a template rule.
+
+```
+  A template defines a custom name that acts like a function. It provides a way
+  to add to the built-in target types.
+
+  The template() function is used to declare a template. To invoke the
+  template, just use the name of the template like any other target type.
+
+  Often you will want to declare your template in a special file that other
+  files will import (see "gn help import") so your template rule can be shared
+  across build files.
+```
+
+#### **Variables and templates**:
+
+```
+  When you call template() it creates a closure around all variables currently
+  in scope with the code in the template block. When the template is invoked,
+  the closure will be executed.
+
+  When the template is invoked, the code in the caller is executed and passed
+  to the template code as an implicit "invoker" variable. The template uses
+  this to read state out of the invoking code.
+
+  One thing explicitly excluded from the closure is the "current directory"
+  against which relative file names are resolved. The current directory will be
+  that of the invoking code, since typically that code specifies the file
+  names. This means all files internal to the template should use absolute
+  names.
+
+  A template will typically forward some or all variables from the invoking
+  scope to a target that it defines. Often, such variables might be optional.
+  Use the pattern:
+
+    if (defined(invoker.deps)) {
+      deps = invoker.deps
+    }
+
+  The function forward_variables_from() provides a shortcut to forward one or
+  more or possibly all variables in this manner:
+
+    forward_variables_from(invoker, ["deps", "public_deps"])
+```
+
+#### **Target naming**
+
+```
+  Your template should almost always define a built-in target with the name the
+  template invoker specified. For example, if you have an IDL template and
+  somebody does:
+    idl("foo") {...
+  you will normally want this to expand to something defining a source_set or
+  static_library named "foo" (among other things you may need). This way, when
+  another target specifies a dependency on "foo", the static_library or
+  source_set will be linked.
+
+  It is also important that any other targets your template expands to have
+  unique names, or you will get collisions.
+
+  Access the invoking name in your template via the implicit "target_name"
+  variable. This should also be the basis for how other targets that a template
+  expands to ensure uniqueness.
+
+  A typical example would be a template that defines an action to generate some
+  source files, and a source_set to compile that source. Your template would
+  name the source_set "target_name" because that's what you want external
+  targets to depend on to link your code. And you would name the action
+  something like "${target_name}_action" to make it unique. The source set
+  would have a dependency on the action to make it run.
+```
+
+#### **Overriding builtin targets**
+
+```
+  You can use template to redefine a built-in target in which case your template
+  takes a precedence over the built-in one. All uses of the target from within
+  the template definition will refer to the built-in target which makes it
+  possible to extend the behavior of the built-in target:
+
+    template("shared_library") {
+      shared_library(shlib) {
+        forward_variables_from(invoker, "*")
+        ...
+      }
+    }
+```
+
+#### **Example of defining a template**
+
+```
+  template("my_idl") {
+    # Be nice and help callers debug problems by checking that the variables
+    # the template requires are defined. This gives a nice message rather than
+    # giving the user an error about an undefined variable in the file defining
+    # the template
+    #
+    # You can also use defined() to give default values to variables
+    # unspecified by the invoker.
+    assert(defined(invoker.sources),
+           "Need sources in $target_name listing the idl files.")
+
+    # Name of the intermediate target that does the code gen. This must
+    # incorporate the target name so it's unique across template
+    # instantiations.
+    code_gen_target_name = target_name + "_code_gen"
+
+    # Intermediate target to convert IDL to C source. Note that the name is
+    # based on the name the invoker of the template specified. This way, each
+    # time the template is invoked we get a unique intermediate action name
+    # (since all target names are in the global scope).
+    action_foreach(code_gen_target_name) {
+      # Access the scope defined by the invoker via the implicit "invoker"
+      # variable.
+      sources = invoker.sources
+
+      # Note that we need an absolute path for our script file name. The
+      # current directory when executing this code will be that of the invoker
+      # (this is why we can use the "sources" directly above without having to
+      # rebase all of the paths). But if we need to reference a script relative
+      # to the template file, we'll need to use an absolute path instead.
+      script = "//tools/idl/idl_code_generator.py"
+
+      # Tell GN how to expand output names given the sources.
+      # See "gn help source_expansion" for more.
+      outputs = [ "$target_gen_dir/{{source_name_part}}.cc",
+                  "$target_gen_dir/{{source_name_part}}.h" ]
+    }
+
+    # Name the source set the same as the template invocation so instancing
+    # this template produces something that other targets can link to in their
+    # deps.
+    source_set(target_name) {
+      # Generates the list of sources, we get these from the action_foreach
+      # above.
+      sources = get_target_outputs(":$code_gen_target_name")
+
+      # This target depends on the files produced by the above code gen target.
+      deps = [ ":$code_gen_target_name" ]
+    }
+  }
+```
+
+#### **Example of invoking the resulting template**
+
+```
+  # This calls the template code above, defining target_name to be
+  # "foo_idl_files" and "invoker" to be the set of stuff defined in the curly
+  # brackets.
+  my_idl("foo_idl_files") {
+    # Goes into the template as "invoker.sources".
+    sources = [ "foo.idl", "bar.idl" ]
+  }
+
+  # Here is a target that depends on our template.
+  executable("my_exe") {
+    # Depend on the name we gave the template call above. Internally, this will
+    # produce a dependency from executable to the source_set inside the
+    # template (since it has this name), which will in turn depend on the code
+    # gen action.
+    deps = [ ":foo_idl_files" ]
+  }
+```
+### <a name="func_tool"></a>**tool**: Specify arguments to a toolchain tool.
+
+#### **Usage**
+
+```
+  tool(<tool type>) {
+    <tool variables...>
+  }
+```
+
+#### **Tool types**
+
+```
+    Compiler tools:
+      "cc": C compiler
+      "cxx": C++ compiler
+      "cxx_module": C++ compiler used for Clang .modulemap files
+      "objc": Objective C compiler
+      "objcxx": Objective C++ compiler
+      "rc": Resource compiler (Windows .rc files)
+      "asm": Assembler
+      "swift": Swift compiler driver
+
+    Linker tools:
+      "alink": Linker for static libraries (archives)
+      "solink": Linker for shared libraries
+      "link": Linker for executables
+
+    Other tools:
+      "stamp": Tool for creating stamp files
+      "copy": Tool to copy files.
+      "action": Defaults for actions
+
+    Platform specific tools:
+      "copy_bundle_data": [iOS, macOS] Tool to copy files in a bundle.
+      "compile_xcassets": [iOS, macOS] Tool to compile asset catalogs.
+
+    Rust tools:
+      "rust_bin": Tool for compiling Rust binaries
+      "rust_cdylib": Tool for compiling C-compatible dynamic libraries.
+      "rust_dylib": Tool for compiling Rust dynamic libraries.
+      "rust_macro": Tool for compiling Rust procedural macros.
+      "rust_rlib": Tool for compiling Rust libraries.
+      "rust_staticlib": Tool for compiling Rust static libraries.
+```
+
+#### **Tool variables**
+
+```
+    command  [string with substitutions]
+        Valid for: all tools except "action" (required)
+
+        The command to run.
+
+    command_launcher  [string]
+        Valid for: all tools except "action" (optional)
+
+        The prefix with which to launch the command (e.g. the path to a Goma or
+        CCache compiler launcher).
+
+        Note that this prefix will not be included in the compilation database or
+        IDE files generated from the build.
+
+    default_output_dir  [string with substitutions]
+        Valid for: linker tools
+
+        Default directory name for the output file relative to the
+        root_build_dir. It can contain other substitution patterns. This will
+        be the default value for the {{output_dir}} expansion (discussed below)
+        but will be overridden by the "output_dir" variable in a target, if one
+        is specified.
+
+        GN doesn't do anything with this string other than pass it along,
+        potentially with target-specific overrides. It is the tool's job to use
+        the expansion so that the files will be in the right place.
+
+    default_output_extension  [string]
+        Valid for: linker tools
+
+        Extension for the main output of a linkable tool. It includes the
+        leading dot. This will be the default value for the
+        {{output_extension}} expansion (discussed below) but will be overridden
+        by by the "output extension" variable in a target, if one is specified.
+        Empty string means no extension.
+
+        GN doesn't actually do anything with this extension other than pass it
+        along, potentially with target-specific overrides. One would typically
+        use the {{output_extension}} value in the "outputs" to read this value.
+
+        Example: default_output_extension = ".exe"
+
+    depfile  [string with substitutions]
+        Valid for: compiler tools (optional)
+
+        If the tool can write ".d" files, this specifies the name of the
+        resulting file. These files are used to list header file dependencies
+        (or other implicit input dependencies) that are discovered at build
+        time. See also "depsformat".
+
+        Example: depfile = "{{output}}.d"
+
+    depsformat  [string]
+        Valid for: compiler tools (when depfile is specified)
+
+        Format for the deps outputs. This is either "gcc" or "msvc". See the
+        ninja documentation for "deps" for more information.
+
+        Example: depsformat = "gcc"
+
+    description  [string with substitutions, optional]
+        Valid for: all tools
+
+        What to print when the command is run.
+
+        Example: description = "Compiling {{source}}"
+
+    exe_output_extension [string, optional, rust tools only]
+    rlib_output_extension [string, optional, rust tools only]
+    dylib_output_extension [string, optional, rust tools only]
+    cdylib_output_extension [string, optional, rust tools only]
+    rust_proc_macro_output_extension [string, optional, rust tools only]
+        Valid for: Rust tools
+
+        These specify the default tool output for each of the crate types.
+        The default is empty for executables, shared, and static libraries and
+        ".rlib" for rlibs. Note that the Rust compiler complains with an error
+        if external crates do not take the form `lib<name>.rlib` or
+        `lib<name>.<shared_extension>`, where `<shared_extension>` is `.so`,
+        `.dylib`, or `.dll` as appropriate for the platform.
+
+    lib_switch  [string, optional, link tools only]
+    lib_dir_switch  [string, optional, link tools only]
+        Valid for: Linker tools except "alink"
+
+        These strings will be prepended to the libraries and library search
+        directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          lib_switch = "-l"
+          lib_dir_switch = "-L"
+        then the "{{libs}}" expansion for
+          [ "freetype", "expat" ]
+        would be
+          "-lfreetype -lexpat".
+
+    framework_switch [string, optional, link tools only]
+    weak_framework_switch [string, optional, link tools only]
+    framework_dir_switch [string, optional, link tools only]
+        Valid for: Linker tools
+
+        These strings will be prepended to the frameworks and framework search
+        path directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          framework_switch = "-framework "
+          weak_framework_switch = "-weak_framework "
+          framework_dir_switch = "-F"
+        and:
+          framework_dirs = [ "$root_out_dir" ]
+          frameworks = [ "UIKit.framework", "Foo.framework" ]
+          weak_frameworks = [ "MediaPlayer.framework" ]
+        would be:
+          "-F. -framework UIKit -framework Foo -weak_framework MediaPlayer"
+
+    swiftmodule_switch [string, optional, link tools only]
+        Valid for: Linker tools except "alink"
+
+        The string will be prependend to the path to the .swiftmodule files
+        that are embedded in the linker output.
+
+        If you specified:
+          swiftmodule_swift = "-Wl,-add_ast_path,"
+        then the "{{swiftmodules}}" expansion for
+          [ "obj/foo/Foo.swiftmodule" ]
+        would be
+          "-Wl,-add_ast_path,obj/foo/Foo.swiftmodule"
+
+    outputs  [list of strings with substitutions]
+        Valid for: Linker and compiler tools (required)
+
+        An array of names for the output files the tool produces. These are
+        relative to the build output directory. There must always be at least
+        one output file. There can be more than one output (a linker might
+        produce a library and an import library, for example).
+
+        This array just declares to GN what files the tool will produce. It is
+        your responsibility to specify the tool command that actually produces
+        these files.
+
+        If you specify more than one output for shared library links, you
+        should consider setting link_output, depend_output, and
+        runtime_outputs.
+
+        Example for a compiler tool that produces .obj files:
+          outputs = [
+            "{{source_out_dir}}/{{source_name_part}}.obj"
+          ]
+
+        Example for a linker tool that produces a .dll and a .lib. The use of
+        {{target_output_name}}, {{output_extension}} and {{output_dir}} allows
+        the target to override these values.
+          outputs = [
+            "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+            "{{output_dir}}/{{target_output_name}}.lib",
+          ]
+
+    partial_outputs  [list of strings with substitutions]
+        Valid for: "swift" only
+
+        An array of names for the partial outputs the tool produces. These
+        are relative to the build output directory. The expansion will be
+        evaluated for each file listed in the "sources" of the target.
+
+        This is used to deal with whole module optimization, allowing to
+        list one object file per source file when whole module optimization
+        is disabled.
+
+    pool [label, optional]
+        Valid for: all tools (optional)
+
+        Label of the pool to use for the tool. Pools are used to limit the
+        number of tasks that can execute concurrently during the build.
+
+        See also "gn help pool".
+
+    link_output  [string with substitutions]
+    depend_output  [string with substitutions]
+        Valid for: "solink" only (optional)
+
+        These two files specify which of the outputs from the solink tool
+        should be used for linking and dependency tracking. These should match
+        entries in the "outputs". If unspecified, the first item in the
+        "outputs" array will be used for all. See "Separate linking and
+        dependencies for shared libraries" below for more.
+
+        On Windows, where the tools produce a .dll shared library and a .lib
+        import library, you will want the first two to be the import library
+        and the third one to be the .dll file. On Linux, if you're not doing
+        the separate linking/dependency optimization, all of these should be
+        the .so output.
+
+    output_prefix  [string]
+        Valid for: Linker tools (optional)
+
+        Prefix to use for the output name. Defaults to empty. This prefix will
+        be prepended to the name of the target (or the output_name if one is
+        manually specified for it) if the prefix is not already there. The
+        result will show up in the {{output_name}} substitution pattern.
+
+        Individual targets can opt-out of the output prefix by setting:
+          output_prefix_override = true
+        (see "gn help output_prefix_override").
+
+        This is typically used to prepend "lib" to libraries on
+        Posix systems:
+          output_prefix = "lib"
+
+    precompiled_header_type  [string]
+        Valid for: "cc", "cxx", "objc", "objcxx"
+
+        Type of precompiled headers. If undefined or the empty string,
+        precompiled headers will not be used for this tool. Otherwise use "gcc"
+        or "msvc".
+
+        For precompiled headers to be used for a given target, the target (or a
+        config applied to it) must also specify a "precompiled_header" and, for
+        "msvc"-style headers, a "precompiled_source" value. If the type is
+        "gcc", then both "precompiled_header" and "precompiled_source" must
+        resolve to the same file, despite the different formats required for
+        each."
+
+        See "gn help precompiled_header" for more.
+
+    restat  [boolean]
+        Valid for: all tools (optional, defaults to false)
+
+        Requests that Ninja check the file timestamp after this tool has run to
+        determine if anything changed. Set this if your tool has the ability to
+        skip writing output if the output file has not changed.
+
+        Normally, Ninja will assume that when a tool runs the output be new and
+        downstream dependents must be rebuild. When this is set to trye, Ninja
+        can skip rebuilding downstream dependents for input changes that don't
+        actually affect the output.
+
+        Example:
+          restat = true
+
+    rspfile  [string with substitutions]
+        Valid for: all tools except "action" (optional)
+
+        Name of the response file. If empty, no response file will be
+        used. See "rspfile_content".
+
+    rspfile_content  [string with substitutions]
+        Valid for: all tools except "action" (required when "rspfile" is used)
+
+        The contents to be written to the response file. This may include all
+        or part of the command to send to the tool which allows you to get
+        around OS command-line length limits.
+
+        This example adds the inputs and libraries to a response file, but
+        passes the linker flags directly on the command line:
+          tool("link") {
+            command = "link -o {{output}} {{ldflags}} @{{output}}.rsp"
+            rspfile = "{{output}}.rsp"
+            rspfile_content = "{{inputs}} {{solibs}} {{libs}} {{rlibs}}"
+          }
+
+    runtime_outputs  [string list with substitutions]
+        Valid for: linker tools
+
+        If specified, this list is the subset of the outputs that should be
+        added to runtime deps (see "gn help runtime_deps"). By default (if
+        runtime_outputs is empty or unspecified), it will be the link_output.
+```
+
+#### **Expansions for tool variables**
+
+```
+  All paths are relative to the root build directory, which is the current
+  directory for running all tools. These expansions are available to all tools:
+
+    {{label}}
+        The label of the current target. This is typically used in the
+        "description" field for link tools. The toolchain will be omitted from
+        the label for targets in the default toolchain, and will be included
+        for targets in other toolchains.
+
+    {{label_name}}
+        The short name of the label of the target. This is the part after the
+        colon. For "//foo/bar:baz" this will be "baz". Unlike
+        {{target_output_name}}, this is not affected by the "output_prefix" in
+        the tool or the "output_name" set on the target.
+
+    {{label_no_toolchain}}
+        The label of the current target, never including the toolchain
+        (otherwise, this is identical to {{label}}). This is used as the module
+        name when using .modulemap files.
+
+    {{output}}
+        The relative path and name of the output(s) of the current build step.
+        If there is more than one output, this will expand to a list of all of
+        them. Example: "out/base/my_file.o"
+
+    {{target_gen_dir}}
+    {{target_out_dir}}
+        The directory of the generated file and output directories,
+        respectively, for the current target. There is no trailing slash. See
+        also {{output_dir}} for linker tools. Example: "out/base/test"
+
+    {{target_output_name}}
+        The short name of the current target with no path information, or the
+        value of the "output_name" variable if one is specified in the target.
+        This will include the "output_prefix" if any. See also {{label_name}}.
+
+        Example: "libfoo" for the target named "foo" and an output prefix for
+        the linker tool of "lib".
+
+  Compiler tools have the notion of a single input and a single output, along
+  with a set of compiler-specific flags. The following expansions are
+  available:
+
+    {{asmflags}}
+    {{cflags}}
+    {{cflags_c}}
+    {{cflags_cc}}
+    {{cflags_objc}}
+    {{cflags_objcc}}
+    {{defines}}
+    {{include_dirs}}
+        Strings correspond that to the processed flags/defines/include
+        directories specified for the target.
+        Example: "--enable-foo --enable-bar"
+
+        Defines will be prefixed by "-D" and include directories will be
+        prefixed by "-I" (these work with Posix tools as well as Microsoft
+        ones).
+
+    {{module_deps}}
+    {{module_deps_no_self}}
+        Strings that correspond to the flags necessary to depend upon the Clang
+        modules referenced by the current target. The "_no_self" version doesn't
+        include the module for the current target, and can be used to compile
+        the pcm itself.
+
+    {{source}}
+        The relative path and name of the current input file.
+        Example: "../../base/my_file.cc"
+
+    {{source_file_part}}
+        The file part of the source including the extension (with no directory
+        information).
+        Example: "foo.cc"
+
+    {{source_name_part}}
+        The filename part of the source file with no directory or extension.
+        Example: "foo"
+
+    {{source_gen_dir}}
+    {{source_out_dir}}
+        The directory in the generated file and output directories,
+        respectively, for the current input file. If the source file is in the
+        same directory as the target is declared in, they will will be the same
+        as the "target" versions above. Example: "gen/base/test"
+
+  Linker tools have multiple inputs and (potentially) multiple outputs. The
+  static library tool ("alink") is not considered a linker tool. The following
+  expansions are available:
+
+    {{inputs}}
+    {{inputs_newline}}
+        Expands to the inputs to the link step. This will be a list of object
+        files and static libraries.
+        Example: "obj/foo.o obj/bar.o obj/somelibrary.a"
+
+        The "_newline" version will separate the input files with newlines
+        instead of spaces. This is useful in response files: some linkers can
+        take a "-filelist" flag which expects newline separated files, and some
+        Microsoft tools have a fixed-sized buffer for parsing each line of a
+        response file.
+
+    {{ldflags}}
+        Expands to the processed set of ldflags and library search paths
+        specified for the target.
+        Example: "-m64 -fPIC -pthread -L/usr/local/mylib"
+
+    {{libs}}
+        Expands to the list of system libraries to link to. Each will be
+        prefixed by the "lib_switch".
+
+        Example: "-lfoo -lbar"
+
+    {{output_dir}}
+        The value of the "output_dir" variable in the target, or the the value
+        of the "default_output_dir" value in the tool if the target does not
+        override the output directory. This will be relative to the
+        root_build_dir and will not end in a slash. Will be "." for output to
+        the root_build_dir.
+
+        This is subtly different than {{target_out_dir}} which is defined by GN
+        based on the target's path and not overridable. {{output_dir}} is for
+        the final output, {{target_out_dir}} is generally for object files and
+        other outputs.
+
+        Usually {{output_dir}} would be defined in terms of either
+        {{target_out_dir}} or {{root_out_dir}}
+
+    {{output_extension}}
+        The value of the "output_extension" variable in the target, or the
+        value of the "default_output_extension" value in the tool if the target
+        does not specify an output extension.
+        Example: ".so"
+
+    {{solibs}}
+        Extra libraries from shared library dependencies not specified in the
+        {{inputs}}. This is the list of link_output files from shared libraries
+        (if the solink tool specifies a "link_output" variable separate from
+        the "depend_output").
+
+        These should generally be treated the same as libs by your tool.
+
+        Example: "libfoo.so libbar.so"
+
+    {{rlibs}}
+        Any Rust .rlibs which need to be linked into a final C++ target.
+        These should be treated as {{inputs}} except that sometimes
+        they might have different linker directives applied.
+
+        Example: "obj/foo/libfoo.rlib"
+
+    {{frameworks}}
+        Shared libraries packaged as framework bundle. This is principally
+        used on Apple's platforms (macOS and iOS). All name must be ending
+        with ".framework" suffix; the suffix will be stripped when expanding
+        {{frameworks}} and each item will be preceded by "-framework" or
+        "-weak_framework".
+
+    {{swiftmodules}}
+        Swift .swiftmodule files that needs to be embedded into the binary.
+        This is necessary to correctly link with object generated by the
+        Swift compiler (the .swiftmodule file cannot be embedded in object
+        files directly). Those will be prefixed with "swiftmodule_switch"
+        value.
+
+  The static library ("alink") tool allows {{arflags}} plus the common tool
+  substitutions.
+
+  The copy tool allows the common compiler/linker substitutions, plus
+  {{source}} which is the source of the copy. The stamp tool allows only the
+  common tool substitutions.
+
+  The copy_bundle_data and compile_xcassets tools only allows the common tool
+  substitutions. Both tools are required to create iOS/macOS bundles and need
+  only be defined on those platforms.
+
+  The copy_bundle_data tool will be called with one source and needs to copy
+  (optionally optimizing the data representation) to its output. It may be
+  called with a directory as input and it needs to be recursively copied.
+
+  The compile_xcassets tool will be called with one or more source (each an
+  asset catalog) that needs to be compiled to a single output. The following
+  substitutions are available:
+
+    {{inputs}}
+        Expands to the list of .xcassets to use as input to compile the asset
+        catalog.
+
+    {{bundle_product_type}}
+        Expands to the product_type of the bundle that will contain the
+        compiled asset catalog. Usually corresponds to the product_type
+        property of the corresponding create_bundle target.
+
+    {{bundle_partial_info_plist}}
+        Expands to the path to the partial Info.plist generated by the
+        assets catalog compiler. Usually based on the target_name of
+        the create_bundle target.
+
+    {{xcasset_compiler_flags}}
+        Expands to the list of flags specified in corresponding
+        create_bundle target.
+
+  The Swift tool has multiple input and outputs. It must have exactly one
+  output of .swiftmodule type, but can have one or more object file outputs,
+  in addition to other type of outputs. The following expansions are available:
+
+    {{module_name}}
+        Expands to the string representing the module name of target under
+        compilation (see "module_name" variable).
+
+    {{module_dirs}}
+        Expands to the list of -I<path> for the target Swift module search
+        path computed from target dependencies.
+
+    {{swiftflags}}
+        Expands to the list of strings representing Swift compiler flags.
+
+  Rust tools have the notion of a single input and a single output, along
+  with a set of compiler-specific flags. The following expansions are
+  available:
+
+    {{crate_name}}
+        Expands to the string representing the crate name of target under
+        compilation.
+
+    {{crate_type}}
+        Expands to the string representing the type of crate for the target
+        under compilation.
+
+    {{externs}}
+        Expands to the list of --extern flags needed to include addition Rust
+        libraries in this target. Includes any specified renamed dependencies.
+
+    {{rustdeps}}
+        Expands to the list of -Ldependency=<path> strings needed to compile
+        this target.
+
+    {{rustenv}}
+        Expands to the list of environment variables.
+
+    {{rustflags}}
+        Expands to the list of strings representing Rust compiler flags.
+```
+
+#### **Separate linking and dependencies for shared libraries**
+
+```
+  Shared libraries are special in that not all changes to them require that
+  dependent targets be re-linked. If the shared library is changed but no
+  imports or exports are different, dependent code needn't be relinked, which
+  can speed up the build.
+
+  If your link step can output a list of exports from a shared library and
+  writes the file only if the new one is different, the timestamp of this file
+  can be used for triggering re-links, while the actual shared library would be
+  used for linking.
+
+  You will need to specify
+    restat = true
+  in the linker tool to make this work, so Ninja will detect if the timestamp
+  of the dependency file has changed after linking (otherwise it will always
+  assume that running a command updates the output):
+
+    tool("solink") {
+      command = "..."
+      outputs = [
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC",
+      ]
+      link_output =
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      depend_output =
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC"
+      restat = true
+    }
+```
+
+#### **Example**
+
+```
+  toolchain("my_toolchain") {
+    # Put these at the top to apply to all tools below.
+    lib_switch = "-l"
+    lib_dir_switch = "-L"
+
+    tool("cc") {
+      command = "gcc {{source}} -o {{output}}"
+      outputs = [ "{{source_out_dir}}/{{source_name_part}}.o" ]
+      description = "GCC {{source}}"
+    }
+    tool("cxx") {
+      command = "g++ {{source}} -o {{output}}"
+      outputs = [ "{{source_out_dir}}/{{source_name_part}}.o" ]
+      description = "G++ {{source}}"
+    }
+  };
+```
+### <a name="func_toolchain"></a>**toolchain**: Defines a toolchain.
+
+```
+  A toolchain is a set of commands and build flags used to compile the source
+  code. The toolchain() function defines these commands.
+```
+
+#### **Toolchain overview**
+
+```
+  You can have more than one toolchain in use at once in a build and a target
+  can exist simultaneously in multiple toolchains. A build file is executed
+  once for each toolchain it is referenced in so the GN code can vary all
+  parameters of each target (or which targets exist) on a per-toolchain basis.
+
+  When you have a simple build with only one toolchain, the build config file
+  is loaded only once at the beginning of the build. It must call
+  set_default_toolchain() (see "gn help set_default_toolchain") to tell GN the
+  label of the toolchain definition to use. The "toolchain_args" section of the
+  toolchain definition is ignored.
+
+  When a target has a dependency on a target using different toolchain (see "gn
+  help labels" for how to specify this), GN will start a build using that
+  secondary toolchain to resolve the target. GN will load the build config file
+  with the build arguments overridden as specified in the toolchain_args.
+  Because the default toolchain is already known, calls to
+  set_default_toolchain() are ignored.
+
+  To load a file in an alternate toolchain, GN does the following:
+
+    1. Loads the file with the toolchain definition in it (as determined by the
+       toolchain label).
+    2. Re-runs the master build configuration file, applying the arguments
+       specified by the toolchain_args section of the toolchain definition.
+    3. Loads the destination build file in the context of the configuration file
+       in the previous step.
+
+  The toolchain configuration is two-way. In the default toolchain (i.e. the
+  main build target) the configuration flows from the build config file to the
+  toolchain. The build config file looks at the state of the build (OS type,
+  CPU architecture, etc.) and decides which toolchain to use (via
+  set_default_toolchain()). In secondary toolchains, the configuration flows
+  from the toolchain to the build config file: the "toolchain_args" in the
+  toolchain definition specifies the arguments to re-invoke the build.
+```
+
+#### **Functions and variables**
+
+```
+  tool()
+    The tool() function call specifies the commands to run for a given step. See
+    "gn help tool".
+
+  toolchain_args [scope]
+    Overrides for build arguments to pass to the toolchain when invoking it.
+    This is a variable of type "scope" where the variable names correspond to
+    variables in declare_args() blocks.
+
+    When you specify a target using an alternate toolchain, the master build
+    configuration file is re-interpreted in the context of that toolchain.
+    toolchain_args allows you to control the arguments passed into this
+    alternate invocation of the build.
+
+    Any default system arguments or arguments passed in via "gn args" will also
+    be passed to the alternate invocation unless explicitly overridden by
+    toolchain_args.
+
+    The toolchain_args will be ignored when the toolchain being defined is the
+    default. In this case, it's expected you want the default argument values.
+
+    See also "gn help buildargs" for an overview of these arguments.
+
+  propagates_configs [boolean, default=false]
+    Determines whether public_configs and all_dependent_configs in this
+    toolchain propagate to targets in other toolchains.
+
+    When false (the default), this toolchain will not propagate any configs to
+    targets in other toolchains that depend on it targets inside this
+    toolchain. This matches the most common usage of toolchains where they
+    represent different architectures or compilers and the settings that apply
+    to one won't necessarily apply to others.
+
+    When true, configs (public and all-dependent) will cross the boundary out
+    of this toolchain as if the toolchain boundary wasn't there. This only
+    affects one direction of dependencies: a toolchain can't control whether
+    it accepts such configs, only whether it pushes them. The build is
+    responsible for ensuring that any external targets depending on targets in
+    this toolchain are compatible with the compiler flags, etc. that may be
+    propagated.
+
+  deps [string list]
+    Dependencies of this toolchain. These dependencies will be resolved before
+    any target in the toolchain is compiled. To avoid circular dependencies
+    these must be targets defined in another toolchain.
+
+    This is expressed as a list of targets, and generally these targets will
+    always specify a toolchain:
+      deps = [ "//foo/bar:baz(//build/toolchain:bootstrap)" ]
+
+    This concept is somewhat inefficient to express in Ninja (it requires a lot
+    of duplicate of rules) so should only be used when absolutely necessary.
+```
+
+#### **Example of defining a toolchain**
+
+```
+  toolchain("32") {
+    tool("cc") {
+      command = "gcc {{source}}"
+      ...
+    }
+
+    toolchain_args = {
+      use_doom_melon = true  # Doom melon always required for 32-bit builds.
+      current_cpu = "x86"
+    }
+  }
+
+  toolchain("64") {
+    tool("cc") {
+      command = "gcc {{source}}"
+      ...
+    }
+
+    toolchain_args = {
+      # use_doom_melon is not overridden here, it will take the default.
+      current_cpu = "x64"
+    }
+  }
+```
+
+#### **Example of cross-toolchain dependencies**
+
+```
+  If a 64-bit target wants to depend on a 32-bit binary, it would specify a
+  dependency using data_deps (data deps are like deps that are only needed at
+  runtime and aren't linked, since you can't link a 32-bit and a 64-bit
+  library).
+
+    executable("my_program") {
+      ...
+      if (target_cpu == "x64") {
+        # The 64-bit build needs this 32-bit helper.
+        data_deps = [ ":helper(//toolchains:32)" ]
+      }
+    }
+
+    if (target_cpu == "x86") {
+      # Our helper library is only compiled in 32-bits.
+      shared_library("helper") {
+        ...
+      }
+    }
+```
+### <a name="func_write_file"></a>**write_file**: Write a file to disk.
+
+```
+  write_file(filename, data, output_conversion = "")
+
+  If data is a list, the list will be written one-item-per-line with no quoting
+  or brackets.
+
+  If the file exists and the contents are identical to that being written, the
+  file will not be updated. This will prevent unnecessary rebuilds of targets
+  that depend on this file.
+
+  One use for write_file is to write a list of inputs to an script that might
+  be too long for the command line. However, it is preferable to use response
+  files for this purpose. See "gn help response_file_contents".
+```
+
+#### **Arguments**
+
+```
+  filename
+      Filename to write. This must be within the output directory.
+
+  data
+      The list or string to write.
+
+  output_conversion
+    Controls how the output is written. See "gn help io_conversion".
+```
+## <a name="predefined_variables"></a>Built-in predefined variables
+
+### <a name="var_current_cpu"></a>**current_cpu**: The processor architecture of the current toolchain.
+
+```
+  The build configuration usually sets this value based on the value of
+  "host_cpu" (see "gn help host_cpu") and then threads this through the
+  toolchain definitions to ensure that it always reflects the appropriate
+  value.
+
+  This value is not used internally by GN for any purpose. It is set to the
+  empty string ("") by default but is declared so that it can be overridden on
+  the command line if so desired.
+
+  See "gn help target_cpu" for a list of common values returned.
+```
+### <a name="var_current_os"></a>**current_os**: The operating system of the current toolchain.
+
+```
+  The build configuration usually sets this value based on the value of
+  "target_os" (see "gn help target_os"), and then threads this through the
+  toolchain definitions to ensure that it always reflects the appropriate
+  value.
+
+  This value is not used internally by GN for any purpose. It is set to the
+  empty string ("") by default but is declared so that it can be overridden on
+  the command line if so desired.
+
+  See "gn help target_os" for a list of common values returned.
+```
+### <a name="var_current_toolchain"></a>**current_toolchain**: Label of the current toolchain.
+
+```
+  A fully-qualified label representing the current toolchain. You can use this
+  to make toolchain-related decisions in the build. See also
+  "default_toolchain".
+```
+
+#### **Example**
+
+```
+  if (current_toolchain == "//build:64_bit_toolchain") {
+    executable("output_thats_64_bit_only") {
+      ...
+```
+### <a name="var_default_toolchain"></a>**default_toolchain**: [string] Label of the default toolchain.
+
+```
+  A fully-qualified label representing the default toolchain, which may not
+  necessarily be the current one (see "current_toolchain").
+```
+### <a name="var_gn_version"></a>**gn_version**: [number] The version of gn.
+
+```
+  Corresponds to the number printed by `gn --version`.
+```
+
+#### **Example**
+
+```
+  assert(gn_version >= 1700, "need GN version 1700 for the frobulate feature")
+```
+### <a name="var_host_cpu"></a>**host_cpu**: The processor architecture that GN is running on.
+
+```
+  This is value is exposed so that cross-compile toolchains can access the host
+  architecture when needed.
+
+  The value should generally be considered read-only, but it can be overridden
+  in order to handle unusual cases where there might be multiple plausible
+  values for the host architecture (e.g., if you can do either 32-bit or 64-bit
+  builds). The value is not used internally by GN for any purpose.
+```
+
+#### **Some possible values**
+
+```
+  - "x64"
+  - "x86"
+```
+### <a name="var_host_os"></a>**host_os**: [string] The operating system that GN is running on.
+
+```
+  This value is exposed so that cross-compiles can access the host build
+  system's settings.
+
+  This value should generally be treated as read-only. It, however, is not used
+  internally by GN for any purpose.
+```
+
+#### **Some possible values**
+
+```
+  - "linux"
+  - "mac"
+  - "win"
+```
+### <a name="var_invoker"></a>**invoker**: [string] The invoking scope inside a template.
+
+```
+  Inside a template invocation, this variable refers to the scope of the
+  invoker of the template. Outside of template invocations, this variable is
+  undefined.
+
+  All of the variables defined inside the template invocation are accessible as
+  members of the "invoker" scope. This is the way that templates read values
+  set by the callers.
+
+  This is often used with "defined" to see if a value is set on the invoking
+  scope.
+
+  See "gn help template" for more examples.
+```
+
+#### **Example**
+
+```
+  template("my_template") {
+    print(invoker.sources)       # Prints [ "a.cc", "b.cc" ]
+    print(defined(invoker.foo))  # Prints false.
+    print(defined(invoker.bar))  # Prints true.
+  }
+
+  my_template("doom_melon") {
+    sources = [ "a.cc", "b.cc" ]
+    bar = 123
+  }
+```
+### <a name="var_python_path"></a>**python_path**: Absolute path of Python.
+
+```
+  Normally used in toolchain definitions if running some command requires
+  Python. You will normally not need this when invoking scripts since GN
+  automatically finds it for you.
+```
+### <a name="var_root_build_dir"></a>**root_build_dir**: [string] Directory where build commands are run.
+
+```
+  This is the root build output directory which will be the current directory
+  when executing all compilers and scripts.
+
+  Most often this is used with rebase_path (see "gn help rebase_path") to
+  convert arguments to be relative to a script's current directory.
+```
+### <a name="var_root_gen_dir"></a>**root_gen_dir**: Directory for the toolchain's generated files.
+
+```
+  Absolute path to the root of the generated output directory tree for the
+  current toolchain. An example would be "//out/Debug/gen" for the default
+  toolchain, or "//out/Debug/arm/gen" for the "arm" toolchain.
+
+  This is primarily useful for setting up include paths for generated files. If
+  you are passing this to a script, you will want to pass it through
+  rebase_path() (see "gn help rebase_path") to convert it to be relative to the
+  build directory.
+
+  See also "target_gen_dir" which is usually a better location for generated
+  files. It will be inside the root generated dir.
+```
+### <a name="var_root_out_dir"></a>**root_out_dir**: [string] Root directory for toolchain output files.
+
+```
+  Absolute path to the root of the output directory tree for the current
+  toolchain. It will not have a trailing slash.
+
+  For the default toolchain this will be the same as the root_build_dir. An
+  example would be "//out/Debug" for the default toolchain, or
+  "//out/Debug/arm" for the "arm" toolchain.
+
+  This is primarily useful for setting up script calls. If you are passing this
+  to a script, you will want to pass it through rebase_path() (see "gn help
+  rebase_path") to convert it to be relative to the build directory.
+
+  See also "target_out_dir" which is usually a better location for output
+  files. It will be inside the root output dir.
+```
+
+#### **Example**
+
+```
+  action("myscript") {
+    # Pass the output dir to the script.
+    args = [ "-o", rebase_path(root_out_dir, root_build_dir) ]
+  }
+```
+### <a name="var_target_cpu"></a>**target_cpu**: The desired cpu architecture for the build.
+
+```
+  This value should be used to indicate the desired architecture for the
+  primary objects of the build. It will match the cpu architecture of the
+  default toolchain, but not necessarily the current toolchain.
+
+  In many cases, this is the same as "host_cpu", but in the case of
+  cross-compiles, this can be set to something different. This value is
+  different from "current_cpu" in that it does not change based on the current
+  toolchain. When writing rules, "current_cpu" should be used rather than
+  "target_cpu" most of the time.
+
+  This value is not used internally by GN for any purpose, so it may be set to
+  whatever value is needed for the build. GN defaults this value to the empty
+  string ("") and the configuration files should set it to an appropriate value
+  (e.g., setting it to the value of "host_cpu") if it is not overridden on the
+  command line or in the args.gn file.
+```
+
+#### **Possible values**
+
+```
+  - "x86"
+  - "x64"
+  - "arm"
+  - "arm64"
+  - "mipsel"
+```
+### <a name="var_target_gen_dir"></a>**target_gen_dir**: Directory for a target's generated files.
+
+```
+  Absolute path to the target's generated file directory. This will be the
+  "root_gen_dir" followed by the relative path to the current build file. If
+  your file is in "//tools/doom_melon" then target_gen_dir would be
+  "//out/Debug/gen/tools/doom_melon". It will not have a trailing slash.
+
+  This is primarily useful for setting up include paths for generated files. If
+  you are passing this to a script, you will want to pass it through
+  rebase_path() (see "gn help rebase_path") to convert it to be relative to the
+  build directory.
+
+  See also "gn help root_gen_dir".
+```
+
+#### **Example**
+
+```
+  action("myscript") {
+    # Pass the generated output dir to the script.
+    args = [ "-o", rebase_path(target_gen_dir, root_build_dir) ]
+  }
+```
+### <a name="var_target_name"></a>**target_name**: [string] The name of the current target.
+
+```
+  Inside a target or template invocation, this variable refers to the name
+  given to the target or template invocation. Outside of these, this variable
+  is undefined.
+
+  This is most often used in template definitions to name targets defined in
+  the template based on the name of the invocation. This is necessary both to
+  ensure generated targets have unique names and to generate a target with the
+  exact name of the invocation that other targets can depend on.
+
+  Be aware that this value will always reflect the innermost scope. So when
+  defining a target inside a template, target_name will refer to the target
+  rather than the template invocation. To get the name of the template
+  invocation in this case, you should save target_name to a temporary variable
+  outside of any target definitions.
+
+  See "gn help template" for more examples.
+```
+
+#### **Example**
+
+```
+  executable("doom_melon") {
+    print(target_name)    # Prints "doom_melon".
+  }
+
+  template("my_template") {
+    print(target_name)    # Prints "space_ray" when invoked below.
+
+    executable(target_name + "_impl") {
+      print(target_name)  # Prints "space_ray_impl".
+    }
+  }
+
+  my_template("space_ray") {
+  }
+```
+### <a name="var_target_os"></a>**target_os**: The desired operating system for the build.
+
+```
+  This value should be used to indicate the desired operating system for the
+  primary object(s) of the build. It will match the OS of the default
+  toolchain.
+
+  In many cases, this is the same as "host_os", but in the case of
+  cross-compiles, it may be different. This variable differs from "current_os"
+  in that it can be referenced from inside any toolchain and will always return
+  the initial value.
+
+  This should be set to the most specific value possible. So, "android" or
+  "chromeos" should be used instead of "linux" where applicable, even though
+  Android and ChromeOS are both Linux variants. This can mean that one needs to
+  write
+
+      if (target_os == "android" || target_os == "linux") {
+          # ...
+      }
+
+  and so forth.
+
+  This value is not used internally by GN for any purpose, so it may be set to
+  whatever value is needed for the build. GN defaults this value to the empty
+  string ("") and the configuration files should set it to an appropriate value
+  (e.g., setting it to the value of "host_os") if it is not set via the command
+  line or in the args.gn file.
+```
+
+#### **Possible values**
+
+```
+  - "android"
+  - "chromeos"
+  - "ios"
+  - "linux"
+  - "nacl"
+  - "mac"
+  - "win"
+```
+### <a name="var_target_out_dir"></a>**target_out_dir**: [string] Directory for target output files.
+
+```
+  Absolute path to the target's generated file directory. If your current
+  target is in "//tools/doom_melon" then this value might be
+  "//out/Debug/obj/tools/doom_melon". It will not have a trailing slash.
+
+  This is primarily useful for setting up arguments for calling scripts. If you
+  are passing this to a script, you will want to pass it through rebase_path()
+  (see "gn help rebase_path") to convert it to be relative to the build
+  directory.
+
+  See also "gn help root_out_dir".
+```
+
+#### **Example**
+
+```
+  action("myscript") {
+    # Pass the output dir to the script.
+    args = [ "-o", rebase_path(target_out_dir, root_build_dir) ]
+  }
+```
+## <a name="target_variables"></a>Variables you set in targets
+
+### <a name="var_aliased_deps"></a>**aliased_deps**: [scope] Set of crate-dependency pairs.
+
+```
+  Valid for `rust_library` targets and `executable`, `static_library`, and
+  `shared_library` targets that contain Rust sources.
+
+  A scope, each key indicating the renamed crate and the corresponding value
+  specifying the label of the dependency producing the relevant binary.
+
+  All dependencies listed in this field *must* be listed as deps of the target.
+
+    executable("foo") {
+      sources = [ "main.rs" ]
+      deps = [ "//bar" ]
+    }
+
+  This target would compile the `foo` crate with the following `extern` flag:
+  `rustc ...command... --extern bar=<build_out_dir>/obj/bar`
+
+    executable("foo") {
+      sources = [ "main.rs" ]
+      deps = [ ":bar" ]
+      aliased_deps = {
+        bar_renamed = ":bar"
+      }
+    }
+
+  With the addition of `aliased_deps`, above target would instead compile with:
+  `rustc ...command... --extern bar_renamed=<build_out_dir>/obj/bar`
+```
+### <a name="var_all_dependent_configs"></a>**all_dependent_configs**: Configs to be forced on dependents.
+
+```
+  A list of config labels.
+
+  All targets depending on this one, and recursively, all targets depending on
+  those, will have the configs listed in this variable added to them. These
+  configs will also apply to the current target.
+
+  This addition happens in a second phase once a target and all of its
+  dependencies have been resolved. Therefore, a target will not see these
+  force-added configs in their "configs" variable while the script is running,
+  and they can not be removed. As a result, this capability should generally
+  only be used to add defines and include directories necessary to compile a
+  target's headers.
+
+  See also "public_configs".
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_allow_circular_includes_from"></a>**allow_circular_includes_from**: Permit includes from deps.
+
+```
+  A list of target labels. Must be a subset of the target's "deps". These
+  targets will be permitted to include headers from the current target despite
+  the dependency going in the opposite direction.
+
+  When you use this, both targets must be included in a final binary for it to
+  link. To keep linker errors from happening, it is good practice to have all
+  external dependencies depend only on one of the two targets, and to set the
+  visibility on the other to enforce this. Thus the targets will always be
+  linked together in any output.
+```
+
+#### **Details**
+
+```
+  Normally, for a file in target A to include a file from target B, A must list
+  B as a dependency. This invariant is enforced by the "gn check" command (and
+  the --check flag to "gn gen" -- see "gn help check").
+
+  Sometimes, two targets might be the same unit for linking purposes (two
+  source sets or static libraries that would always be linked together in a
+  final executable or shared library) and they each include headers from the
+  other: you want A to be able to include B's headers, and B to include A's
+  headers. This is not an ideal situation but is sometimes unavoidable.
+
+  This list, if specified, lists which of the dependencies of the current
+  target can include header files from the current target. That is, if A
+  depends on B, B can only include headers from A if it is in A's
+  allow_circular_includes_from list. Normally includes must follow the
+  direction of dependencies, this flag allows them to go in the opposite
+  direction.
+```
+
+#### **Danger**
+
+```
+  In the above example, A's headers are likely to include headers from A's
+  dependencies. Those dependencies may have public_configs that apply flags,
+  defines, and include paths that make those headers work properly.
+
+  With allow_circular_includes_from, B can include A's headers, and
+  transitively from A's dependencies, without having the dependencies that
+  would bring in the public_configs those headers need. The result may be
+  errors or inconsistent builds.
+
+  So when you use allow_circular_includes_from, make sure that any compiler
+  settings, flags, and include directories are the same between both targets
+  (consider putting such things in a shared config they can both reference).
+  Make sure the dependencies are also the same (you might consider a group to
+  collect such dependencies they both depend on).
+```
+
+#### **Example**
+
+```
+  source_set("a") {
+    deps = [ ":b", ":a_b_shared_deps" ]
+    allow_circular_includes_from = [ ":b" ]
+    ...
+  }
+
+  source_set("b") {
+    deps = [ ":a_b_shared_deps" ]
+    # Sources here can include headers from a despite lack of deps.
+    ...
+  }
+
+  group("a_b_shared_deps") {
+    public_deps = [ ":c" ]
+  }
+```
+### <a name="var_arflags"></a>**arflags**: Arguments passed to static_library archiver.
+
+```
+  A list of flags passed to the archive/lib command that creates static
+  libraries.
+
+  arflags are NOT pushed to dependents, so applying arflags to source sets or
+  any other target type will be a no-op. As with ldflags, you could put the
+  arflags in a config and set that as a public or "all dependent" config, but
+  that will likely not be what you want. If you have a chain of static
+  libraries dependent on each other, this can cause the flags to propagate up
+  to other static libraries. Due to the nature of how arflags are typically
+  used, you will normally want to apply them directly on static_library targets
+  themselves.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_args"></a>**args**: (target variable) Arguments passed to an action.
+
+```
+  For action and action_foreach targets, args is the list of arguments to pass
+  to the script. Typically you would use source expansion (see "gn help
+  source_expansion") to insert the source file names.
+
+  See also "gn help action" and "gn help action_foreach".
+```
+### <a name="var_asmflags"></a>**asmflags**: Flags passed to the assembler.
+
+```
+  A list of strings.
+
+  "asmflags" are passed to any invocation of a tool that takes an .asm or .S
+  file as input.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_assert_no_deps"></a>**assert_no_deps**: Ensure no deps on these targets.
+
+```
+  A list of label patterns.
+
+  This list is a list of patterns that must not match any of the transitive
+  dependencies of the target. These include all public, private, and data
+  dependencies, and cross shared library boundaries. This allows you to express
+  that undesirable code isn't accidentally added to downstream dependencies in
+  a way that might otherwise be difficult to notice.
+
+  Checking does not cross executable boundaries. If a target depends on an
+  executable, it's assumed that the executable is a tool that is producing part
+  of the build rather than something that is linked and distributed. This
+  allows assert_no_deps to express what is distributed in the final target
+  rather than depend on the internal build steps (which may include
+  non-distributable code).
+
+  See "gn help label_pattern" for the format of the entries in the list. These
+  patterns allow blacklisting individual targets or whole directory
+  hierarchies.
+
+  Sometimes it is desirable to enforce that many targets have no dependencies
+  on a target or set of targets. One efficient way to express this is to create
+  a group with the assert_no_deps rule on it, and make that group depend on all
+  targets you want to apply that assertion to.
+```
+
+#### **Example**
+
+```
+  executable("doom_melon") {
+    deps = [ "//foo:bar" ]
+    ...
+    assert_no_deps = [
+      "//evil/*",  # Don't link any code from the evil directory.
+      "//foo:test_support",  # This target is also disallowed.
+    ]
+  }
+```
+### <a name="var_bridge_header"></a>**bridge_header**: [string] Path to C/Objective-C compatibility header.
+
+```
+  Valid for binary targets that contain Swift sources.
+
+  Path to an header that includes C/Objective-C functions and types that
+  needs to be made available to the Swift module.
+```
+### <a name="var_bundle_contents_dir"></a>**bundle_contents_dir**: Expansion of {{bundle_contents_dir}} in
+```
+                             create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_contents_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+```
+### <a name="var_bundle_deps_filter"></a>**bundle_deps_filter**: [label list] A list of labels that are filtered out.
+
+```
+  A list of target labels.
+
+  This list contains target label patterns that should be filtered out when
+  creating the bundle. Any target matching one of those label will be removed
+  from the dependencies of the create_bundle target.
+
+  This is mostly useful when creating application extension bundle as the
+  application extension has access to runtime resources from the application
+  bundle and thus do not require a second copy.
+
+  See "gn help create_bundle" for more information.
+```
+
+#### **Example**
+
+```
+  create_bundle("today_extension") {
+    deps = [
+      "//base"
+    ]
+    bundle_root_dir = "$root_out_dir/today_extension.appex"
+    bundle_deps_filter = [
+      # The extension uses //base but does not use any function calling into
+      # third_party/icu and thus does not need the icudtl.dat file.
+      "//third_party/icu:icudata",
+    ]
+  }
+```
+### <a name="var_bundle_executable_dir"></a>**bundle_executable_dir**
+
+```
+  bundle_executable_dir: Expansion of {{bundle_executable_dir}} in
+                         create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_executable_dir}} of the "bundle_data" target it depends on. This
+  must correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+```
+### <a name="var_bundle_resources_dir"></a>**bundle_resources_dir**
+
+```
+  bundle_resources_dir: Expansion of {{bundle_resources_dir}} in
+                        create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_resources_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+```
+### <a name="var_bundle_root_dir"></a>**bundle_root_dir**: Expansion of {{bundle_root_dir}} in create_bundle.
+
+```
+  A string corresponding to a path in root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_root_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under root_build_dir.
+```
+
+#### **Example**
+
+```
+  bundle_data("info_plist") {
+    sources = [ "Info.plist" ]
+    outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
+  }
+
+  create_bundle("doom_melon.app") {
+    deps = [ ":info_plist" ]
+    bundle_root_dir = "${root_build_dir}/doom_melon.app"
+    bundle_contents_dir = "${bundle_root_dir}/Contents"
+    bundle_resources_dir = "${bundle_contents_dir}/Resources"
+    bundle_executable_dir = "${bundle_contents_dir}/MacOS"
+  }
+```
+### <a name="var_cflags"></a>**cflags***: Flags passed to the C compiler.
+
+```
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_cflags_c"></a>**cflags***: Flags passed to the C compiler.
+
+```
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_cflags_cc"></a>**cflags***: Flags passed to the C compiler.
+
+```
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_cflags_objc"></a>**cflags***: Flags passed to the C compiler.
+
+```
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_cflags_objcc"></a>**cflags***: Flags passed to the C compiler.
+
+```
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_check_includes"></a>**check_includes**: [boolean] Controls whether a target's files are checked.
+
+```
+  When true (the default), the "gn check" command (as well as "gn gen" with the
+  --check flag) will check this target's sources and headers for proper
+  dependencies.
+
+  When false, the files in this target will be skipped by default. This does
+  not affect other targets that depend on the current target, it just skips
+  checking the includes of the current target's files.
+
+  If there are a few conditionally included headers that trip up checking, you
+  can exclude headers individually by annotating them with "nogncheck" (see "gn
+  help nogncheck").
+
+  The topic "gn help check" has general information on how checking works and
+  advice on how to pass a check in problematic cases.
+```
+
+#### **Example**
+
+```
+  source_set("busted_includes") {
+    # This target's includes are messed up, exclude it from checking.
+    check_includes = false
+    ...
+  }
+```
+### <a name="var_code_signing_args"></a>**code_signing_args**: [string list] Arguments passed to code signing script.
+
+```
+  For create_bundle targets, code_signing_args is the list of arguments to pass
+  to the code signing script. Typically you would use source expansion (see "gn
+  help source_expansion") to insert the source file names.
+
+  See also "gn help create_bundle".
+```
+### <a name="var_code_signing_outputs"></a>**code_signing_outputs**: [file list] Output files for code signing step.
+
+```
+  Outputs from the code signing step of a create_bundle target. Must refer to
+  files in the build directory.
+
+  See also "gn help create_bundle".
+```
+### <a name="var_code_signing_script"></a>**code_signing_script**: [file name] Script for code signing."
+
+```
+  An absolute or buildfile-relative file name of a Python script to run for a
+  create_bundle target to perform code signing step.
+
+  See also "gn help create_bundle".
+```
+### <a name="var_code_signing_sources"></a>**code_signing_sources**: [file list] Sources for code signing step.
+
+```
+  A list of files used as input for code signing script step of a create_bundle
+  target. Non-absolute paths will be resolved relative to the current build
+  file.
+
+  See also "gn help create_bundle".
+```
+### <a name="var_complete_static_lib"></a>**complete_static_lib**: [boolean] Links all deps into a static library.
+
+```
+  A static library normally doesn't include code from dependencies, but instead
+  forwards the static libraries and source sets in its deps up the dependency
+  chain until a linkable target (an executable or shared library) is reached.
+  The final linkable target only links each static library once, even if it
+  appears more than once in its dependency graph.
+
+  In some cases the static library might be the final desired output. For
+  example, you may be producing a static library for distribution to third
+  parties. In this case, the static library should include code for all
+  dependencies in one complete package. However, complete static libraries
+  themselves are never linked into other complete static libraries. All
+  complete static libraries are for distribution and linking them in would
+  cause code duplication in this case. If the static library is not for
+  distribution, it should not be complete.
+
+  GN treats non-complete static libraries as source sets when they are linked
+  into complete static libraries. This is done because some tools like AR do
+  not handle dependent static libraries properly. This makes it easier to write
+  "alink" rules.
+
+  In rare cases it makes sense to list a header in more than one target if it
+  could be considered conceptually a member of both. libraries.
+```
+
+#### **Example**
+
+```
+  static_library("foo") {
+    complete_static_lib = true
+    deps = [ "bar" ]
+  }
+```
+### <a name="var_configs"></a>**configs**: Configs applying to this target or config.
+
+```
+  A list of config labels.
+```
+
+#### **Configs on a target**
+
+```
+  When used on a target, the include_dirs, defines, etc. in each config are
+  appended in the order they appear to the compile command for each file in the
+  target. They will appear after the include_dirs, defines, etc. that the
+  target sets directly.
+
+  Since configs apply after the values set on a target, directly setting a
+  compiler flag will prepend it to the command line. If you want to append a
+  flag instead, you can put that flag in a one-off config and append that
+  config to the target's configs list.
+
+  The build configuration script will generally set up the default configs
+  applying to a given target type (see "set_defaults"). When a target is being
+  defined, it can add to or remove from this list.
+```
+
+#### **Configs on a config**
+
+```
+  It is possible to create composite configs by specifying configs on a config.
+  One might do this to forward values, or to factor out blocks of settings from
+  very large configs into more manageable named chunks.
+
+  In this case, the composite config is expanded to be the concatenation of its
+  own values, and in order, the values from its sub-configs *before* anything
+  else happens. This has some ramifications:
+
+   - A target has no visibility into a config's sub-configs. Target code only
+     sees the name of the composite config. It can't remove sub-configs or opt
+     in to only parts of it. The composite config may not even be defined
+     before the target is.
+
+   - You can get duplication of values if a config is listed twice, say, on a
+     target and in a sub-config that also applies. In other cases, the configs
+     applying to a target are de-duped. It's expected that if a config is
+     listed as a sub-config that it is only used in that context. (Note that
+     it's possible to fix this and de-dupe, but it's not normally relevant and
+     complicates the implementation.)
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  # Configs on a target.
+  source_set("foo") {
+    # Don't use the default RTTI config that BUILDCONFIG applied to us.
+    configs -= [ "//build:no_rtti" ]
+
+    # Add some of our own settings.
+    configs += [ ":mysettings" ]
+  }
+
+  # Create a default_optimization config that forwards to one of a set of more
+  # specialized configs depending on build flags. This pattern is useful
+  # because it allows a target to opt in to either a default set, or a more
+  # specific set, while avoid duplicating the settings in two places.
+  config("super_optimization") {
+    cflags = [ ... ]
+  }
+  config("default_optimization") {
+    if (optimize_everything) {
+      configs = [ ":super_optimization" ]
+    } else {
+      configs = [ ":no_optimization" ]
+    }
+  }
+```
+### <a name="var_contents"></a>**contents**: Contents to write to file.
+
+```
+  The contents of the file for a generated_file target.
+  See "gn help generated_file".
+```
+### <a name="var_crate_name"></a>**crate_name**: [string] The name for the compiled crate.
+
+```
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  If crate_name is not set, then this rule will use the target name.
+```
+### <a name="var_crate_root"></a>**crate_root**: [string] The root source file for a binary or library.
+
+```
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  This file is usually the `main.rs` or `lib.rs` for binaries and libraries,
+  respectively.
+
+  If crate_root is not set, then this rule will look for a lib.rs file (or
+  main.rs for executable) or a single file in sources, if sources contains
+  only one file.
+```
+### <a name="var_crate_type"></a>**crate_type**: [string] The type of linkage to use on a shared_library.
+
+```
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  Options for this field are "cdylib", "staticlib", "proc-macro", and "dylib".
+  This field sets the `crate-type` attribute for the `rustc` tool on static
+  libraries, as well as the appropriate output extension in the
+  `rust_output_extension` attribute. Since outputs must be explicit, the `lib`
+  crate type (where the Rust compiler produces what it thinks is the
+  appropriate library type) is not supported.
+
+  It should be noted that the "dylib" crate type in Rust is unstable in the set
+  of symbols it exposes, and most usages today are potentially wrong and will
+  be broken in the future.
+
+  Static libraries, rust libraries, and executables have this field set
+  automatically.
+```
+### <a name="var_data"></a>**data**: Runtime data file dependencies.
+
+```
+  Lists files or directories required to run the given target. These are
+  typically data files or directories of data files. The paths are interpreted
+  as being relative to the current build file. Since these are runtime
+  dependencies, they do not affect which targets are built or when. To declare
+  input files to a script, use "inputs".
+
+  Appearing in the "data" section does not imply any special handling such as
+  copying them to the output directory. This is just used for declaring runtime
+  dependencies. Runtime dependencies can be queried using the "runtime_deps"
+  category of "gn desc" or written during build generation via
+  "--runtime-deps-list-file".
+
+  GN doesn't require data files to exist at build-time. So actions that produce
+  files that are in turn runtime dependencies can list those generated files
+  both in the "outputs" list as well as the "data" list.
+
+  By convention, directories are listed with a trailing slash:
+    data = [ "test/data/" ]
+  However, no verification is done on these so GN doesn't enforce this. The
+  paths are just rebased and passed along when requested.
+
+  Note: On iOS and macOS, create_bundle targets will not be recursed into when
+  gathering data. See "gn help create_bundle" for details.
+
+  See "gn help runtime_deps" for how these are used.
+```
+### <a name="var_data_deps"></a>**data_deps**: Non-linked dependencies.
+
+```
+  A list of target labels.
+
+  Specifies dependencies of a target that are not actually linked into the
+  current target. Such dependencies will be built and will be available at
+  runtime.
+
+  This is normally used for things like plugins or helper programs that a
+  target needs at runtime.
+
+  Note: On iOS and macOS, create_bundle targets will not be recursed into when
+  gathering data_deps. See "gn help create_bundle" for details.
+
+  See also "gn help deps" and "gn help data".
+```
+
+#### **Example**
+
+```
+  executable("foo") {
+    deps = [ "//base" ]
+    data_deps = [ "//plugins:my_runtime_plugin" ]
+  }
+```
+### <a name="var_data_keys"></a>**data_keys**: Keys from which to collect metadata.
+
+```
+  These keys are used to identify metadata to collect. If a walked target
+  defines this key in its metadata, its value will be appended to the resulting
+  collection.
+
+  See "gn help generated_file".
+```
+### <a name="var_defines"></a>**defines**: C preprocessor defines.
+
+```
+  A list of strings
+
+  These strings will be passed to the C/C++ compiler as #defines. The strings
+  may or may not include an "=" to assign a value.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  defines = [ "AWESOME_FEATURE", "LOG_LEVEL=3" ]
+```
+### <a name="var_depfile"></a>**depfile**: [string] File name for input dependencies for actions.
+
+```
+  If nonempty, this string specifies that the current action or action_foreach
+  target will generate the given ".d" file containing the dependencies of the
+  input. Empty or unset means that the script doesn't generate the files.
+
+  A depfile should be used only when a target depends on files that are not
+  already specified by a target's inputs and sources. Likewise, depfiles should
+  specify only those dependencies not already included in sources or inputs.
+
+  The .d file should go in the target output directory. If you have more than
+  one source file that the script is being run over, you can use the output
+  file expansions described in "gn help action_foreach" to name the .d file
+  according to the input.
+
+  The format is that of a Makefile and all paths must be relative to the root
+  build directory. Only one output may be listed and it must match the first
+  output of the action.
+
+  Although depfiles are created by an action, they should not be listed in the
+  action's "outputs" unless another target will use the file as an input.
+```
+
+#### **Example**
+
+```
+  action_foreach("myscript_target") {
+    script = "myscript.py"
+    sources = [ ... ]
+
+    # Locate the depfile in the output directory named like the
+    # inputs but with a ".d" appended.
+    depfile = "$relative_target_output_dir/{{source_name}}.d"
+
+    # Say our script uses "-o <d file>" to indicate the depfile.
+    args = [ "{{source}}", "-o", depfile ]
+  }
+```
+### <a name="var_deps"></a>**deps**: Private linked dependencies.
+
+```
+  A list of target labels.
+
+  Specifies private dependencies of a target. Private dependencies are
+  propagated up the dependency tree and linked to dependent targets, but do not
+  grant the ability to include headers from the dependency. Public configs are
+  not forwarded.
+```
+
+#### **Details of dependency propagation**
+
+```
+  Source sets, shared libraries, and non-complete static libraries will be
+  propagated up the dependency tree across groups, non-complete static
+  libraries and source sets.
+
+  Executables, shared libraries, and complete static libraries will link all
+  propagated targets and stop propagation. Actions and copy steps also stop
+  propagation, allowing them to take a library as an input but not force
+  dependents to link to it.
+
+  Propagation of all_dependent_configs and public_configs happens independently
+  of target type. all_dependent_configs are always propagated across all types
+  of targets, and public_configs are always propagated across public deps of
+  all types of targets.
+
+  Data dependencies are propagated differently. See "gn help data_deps" and
+  "gn help runtime_deps".
+
+  See also "public_deps".
+```
+### <a name="var_externs"></a>**externs**: [scope] Set of Rust crate-dependency pairs.
+
+```
+  A list, each value being a scope indicating a pair of crate name and the path
+  to the Rust library.
+
+  These libraries will be passed as `--extern crate_name=path` to compiler
+  invocation containing the current target.
+```
+
+#### **Examples**
+
+```
+    executable("foo") {
+      sources = [ "main.rs" ]
+      externs = [{
+        crate_name = "bar",
+        path = "path/to/bar.rlib"
+      }]
+    }
+
+  This target would compile the `foo` crate with the following `extern` flag:
+  `--extern bar=path/to/bar.rlib`.
+```
+### <a name="var_framework_dirs"></a>**framework_dirs**: [directory list] Additional framework search directories.
+
+```
+  A list of source directories.
+
+  The directories in this list will be added to the framework search path for
+  the files in the affected target.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  framework_dirs = [ "src/include", "//third_party/foo" ]
+```
+### <a name="var_frameworks"></a>**frameworks**: [name list] Name of frameworks that must be linked.
+
+```
+  A list of framework names.
+
+  The frameworks named in that list will be linked with any dynamic link
+  type target.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  frameworks = [ "Foundation.framework", "Foo.framework" ]
+```
+### <a name="var_friend"></a>**friend**: Allow targets to include private headers.
+
+```
+  A list of label patterns (see "gn help label_pattern") that allow dependent
+  targets to include private headers. Applies to all binary targets.
+
+  Normally if a target lists headers in the "public" list (see "gn help
+  public"), other headers are implicitly marked as private. Private headers
+  can not be included by other targets, even with a public dependency path.
+  The "gn check" function performs this validation.
+
+  A friend declaration allows one or more targets to include private headers.
+  This is useful for things like unit tests that are closely associated with a
+  target and require internal knowledge without opening up all headers to be
+  included by all dependents.
+
+  A friend target does not allow that target to include headers when no
+  dependency exists. A public dependency path must still exist between two
+  targets to include any headers from a destination target. The friend
+  annotation merely allows the use of headers that would otherwise be
+  prohibited because they are private.
+
+  The friend annotation is matched only against the target containing the file
+  with the include directive. Friend annotations are not propagated across
+  public or private dependencies. Friend annotations do not affect visibility.
+```
+
+#### **Example**
+
+```
+  static_library("lib") {
+    # This target can include our private headers.
+    friend = [ ":unit_tests" ]
+
+    public = [
+      "public_api.h",  # Normal public API for dependent targets.
+    ]
+
+    # Private API and sources.
+    sources = [
+      "a_source_file.cc",
+
+      # Normal targets that depend on this one won't be able to include this
+      # because this target defines a list of "public" headers. Without the
+      # "public" list, all headers are implicitly public.
+      "private_api.h",
+    ]
+  }
+
+  executable("unit_tests") {
+    sources = [
+      # This can include "private_api.h" from the :lib target because it
+      # depends on that target and because of the friend annotation.
+      "my_test.cc",
+    ]
+
+    deps = [
+      ":lib",  # Required for the include to be allowed.
+    ]
+  }
+```
+### <a name="var_include_dirs"></a>**include_dirs**: Additional include directories.
+
+```
+  A list of source directories.
+
+  The directories in this list will be added to the include path for the files
+  in the affected target.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  include_dirs = [ "src/include", "//third_party/foo" ]
+```
+### <a name="var_inputs"></a>**inputs**: Additional compile-time dependencies.
+
+```
+  Inputs are compile-time dependencies of the current target. This means that
+  all inputs must be available before compiling any of the sources or executing
+  any actions.
+
+  Inputs are typically only used for action and action_foreach targets.
+```
+
+#### **Inputs for actions**
+
+```
+  For action and action_foreach targets, inputs should be the inputs to script
+  that don't vary. These should be all .py files that the script uses via
+  imports (the main script itself will be an implicit dependency of the action
+  so need not be listed).
+
+  For action targets, inputs and sources are treated the same, but from a style
+  perspective, it's recommended to follow the same rule as action_foreach and
+  put helper files in the inputs, and the data used by the script (if any) in
+  sources.
+
+  Note that another way to declare input dependencies from an action is to have
+  the action write a depfile (see "gn help depfile"). This allows the script to
+  dynamically write input dependencies, that might not be known until actually
+  executing the script. This is more efficient than doing processing while
+  running GN to determine the inputs, and is easier to keep in-sync than
+  hardcoding the list.
+```
+
+#### **Script input gotchas**
+
+```
+  It may be tempting to write a script that enumerates all files in a directory
+  as inputs. Don't do this! Even if you specify all the files in the inputs or
+  sources in the GN target (or worse, enumerate the files in an exec_script
+  call when running GN, which will be slow), the dependencies will be broken.
+
+  The problem happens if a file is ever removed because the inputs are not
+  listed on the command line to the script. Because the script hasn't changed
+  and all inputs are up to date, the script will not re-run and you will get a
+  stale build. Instead, either list all inputs on the command line to the
+  script, or if there are many, create a separate list file that the script
+  reads. As long as this file is listed in the inputs, the build will detect
+  when it has changed in any way and the action will re-run.
+```
+
+#### **Inputs for binary targets**
+
+```
+  Any input dependencies will be resolved before compiling any sources or
+  linking the target. Normally, all actions that a target depends on will be run
+  before any files in a target are compiled. So if you depend on generated
+  headers, you do not typically need to list them in the inputs section.
+
+  Inputs for binary targets will be treated as implicit dependencies, meaning
+  that changes in any of the inputs will force all sources in the target to be
+  recompiled. If an input only applies to a subset of source files, you may
+  want to split those into a separate target to avoid unnecessary recompiles.
+```
+
+#### **Example**
+
+```
+  action("myscript") {
+    script = "domything.py"
+    inputs = [ "input.data" ]
+  }
+```
+### <a name="var_ldflags"></a>**ldflags**: Flags passed to the linker.
+
+```
+  A list of strings.
+
+  These flags are passed on the command-line to the linker and generally
+  specify various linking options. Most targets will not need these and will
+  use "libs" and "lib_dirs" instead.
+
+  ldflags are NOT pushed to dependents, so applying ldflags to source sets or
+  static libraries will be a no-op. If you want to apply ldflags to dependent
+  targets, put them in a config and set it in the all_dependent_configs or
+  public_configs.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_lib_dirs"></a>**lib_dirs**: Additional library directories.
+
+```
+  A list of directories.
+
+  Specifies additional directories passed to the linker for searching for the
+  required libraries. If an item is not an absolute path, it will be treated as
+  being relative to the current build file.
+
+  libs and lib_dirs work differently than other flags in two respects.
+  First, they are inherited across static library boundaries until a
+  shared library or executable target is reached. Second, they are
+  uniquified so each one is only passed once (the first instance of it
+  will be the one used).
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+
+  For "libs" and "lib_dirs" only, the values propagated from
+  dependencies (as described above) are applied last assuming they
+  are not already in the list.
+```
+
+#### **Example**
+
+```
+  lib_dirs = [ "/usr/lib/foo", "lib/doom_melon" ]
+```
+### <a name="var_libs"></a>**libs**: Additional libraries to link.
+
+```
+  A list of library names or library paths.
+
+  These libraries will be linked into the final binary (executable or shared
+  library) containing the current target.
+
+  libs and lib_dirs work differently than other flags in two respects.
+  First, they are inherited across static library boundaries until a
+  shared library or executable target is reached. Second, they are
+  uniquified so each one is only passed once (the first instance of it
+  will be the one used).
+```
+
+#### **Types of libs**
+
+```
+  There are several different things that can be expressed in libs:
+
+  File paths
+      Values containing '/' will be treated as references to files in the
+      checkout. They will be rebased to be relative to the build directory and
+      specified in the "libs" for linker tools. This facility should be used
+      for libraries that are checked in to the version control. For libraries
+      that are generated by the build, use normal GN deps to link them.
+
+  System libraries
+      Values not containing '/' will be treated as system library names. These
+      will be passed unmodified to the linker and prefixed with the
+      "lib_switch" attribute of the linker tool. Generally you would set the
+      "lib_dirs" so the given library is found. Your BUILD.gn file should not
+      specify the switch (like "-l"): this will be encoded in the "lib_switch"
+      of the tool.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+
+  For "libs" and "lib_dirs" only, the values propagated from
+  dependencies (as described above) are applied last assuming they
+  are not already in the list.
+```
+
+#### **Examples**
+
+```
+  On Windows:
+    libs = [ "ctl3d.lib" ]
+
+  On Linux:
+    libs = [ "ld" ]
+```
+### <a name="var_metadata"></a>**metadata**: Metadata of this target.
+
+```
+  Metadata is a collection of keys and values relating to a particular target.
+  Values must be lists, allowing for sane and predictable collection behavior.
+  Generally, these keys will include three types of lists: lists of ordinary
+  strings, lists of filenames intended to be rebased according to their
+  particular source directory, and lists of target labels intended to be used
+  as barriers to the walk. Verification of these categories occurs at walk time,
+  not creation time (since it is not clear until the walk which values are
+  intended for which purpose).
+```
+
+#### **Example**
+
+```
+  group("doom_melon") {
+    metadata = {
+      # These keys are not built in to GN but are interpreted when consuming
+      # metadata.
+      my_barrier = []
+      my_files = [ "a.txt", "b.txt" ]
+    }
+  }
+```
+### <a name="var_module_name"></a>**module_name**: [string] The name for the compiled module.
+
+```
+  Valid for binary targets that contain Swift sources.
+
+  If module_name is not set, then this rule will use the target name.
+```
+### <a name="var_output_conversion"></a>**output_conversion**: Data format for generated_file targets.
+
+```
+  Controls how the "contents" of a generated_file target is formatted.
+  See "gn help io_conversion".
+```
+### <a name="var_output_dir"></a>**output_dir**: [directory] Directory to put output file in.
+
+```
+  For library and executable targets, overrides the directory for the final
+  output. This must be in the root_build_dir or a child thereof.
+
+  This should generally be in the root_out_dir or a subdirectory thereof (the
+  root_out_dir will be the same as the root_build_dir for the default
+  toolchain, and will be a subdirectory for other toolchains). Not putting the
+  output in a subdirectory of root_out_dir can result in collisions between
+  different toolchains, so you will need to take steps to ensure that your
+  target is only present in one toolchain.
+
+  Normally the toolchain specifies the output directory for libraries and
+  executables (see "gn help tool"). You will have to consult that for the
+  default location. The default location will be used if output_dir is
+  undefined or empty.
+```
+
+#### **Example**
+
+```
+  shared_library("doom_melon") {
+    output_dir = "$root_out_dir/plugin_libs"
+    ...
+  }
+```
+### <a name="var_output_extension"></a>**output_extension**: Value to use for the output's file extension.
+
+```
+  Normally the file extension for a target is based on the target type and the
+  operating system, but in rare cases you will need to override the name (for
+  example to use "libfreetype.so.6" instead of libfreetype.so on Linux).
+
+  This value should not include a leading dot. If undefined, the default
+  specified on the tool will be used. If set to the empty string, no output
+  extension will be used.
+
+  The output_extension will be used to set the "{{output_extension}}" expansion
+  which the linker tool will generally use to specify the output file name. See
+  "gn help tool".
+```
+
+#### **Example**
+
+```
+  shared_library("freetype") {
+    if (is_linux) {
+      # Call the output "libfreetype.so.6"
+      output_extension = "so.6"
+    }
+    ...
+  }
+
+  # On Windows, generate a "mysettings.cpl" control panel applet. Control panel
+  # applets are actually special shared libraries.
+  if (is_win) {
+    shared_library("mysettings") {
+      output_extension = "cpl"
+      ...
+    }
+  }
+```
+### <a name="var_output_name"></a>**output_name**: Define a name for the output file other than the default.
+
+```
+  Normally the output name of a target will be based on the target name, so the
+  target "//foo/bar:bar_unittests" will generate an output file such as
+  "bar_unittests.exe" (using Windows as an example).
+
+  Sometimes you will want an alternate name to avoid collisions or if the
+  internal name isn't appropriate for public distribution.
+
+  The output name should have no extension or prefixes, these will be added
+  using the default system rules. For example, on Linux an output name of "foo"
+  will produce a shared library "libfoo.so". There is no way to override the
+  output prefix of a linker tool on a per- target basis. If you need more
+  flexibility, create a copy target to produce the file you want.
+
+  This variable is valid for all binary output target types.
+```
+
+#### **Example**
+
+```
+  static_library("doom_melon") {
+    output_name = "fluffy_bunny"
+  }
+```
+### <a name="var_output_prefix_override"></a>**output_prefix_override**: Don't use prefix for output name.
+
+```
+  A boolean that overrides the output prefix for a target. Defaults to false.
+
+  Some systems use prefixes for the names of the final target output file. The
+  normal example is "libfoo.so" on Linux for a target named "foo".
+
+  The output prefix for a given target type is specified on the linker tool
+  (see "gn help tool"). Sometimes this prefix is undesired.
+
+  See also "gn help output_extension".
+```
+
+#### **Example**
+
+```
+  shared_library("doom_melon") {
+    # Normally this will produce "libdoom_melon.so" on Linux. Setting this flag
+    # will produce "doom_melon.so".
+    output_prefix_override = true
+    ...
+  }
+```
+### <a name="var_outputs"></a>**outputs**: Output files for actions and copy targets.
+
+```
+  Outputs is valid for "copy", "action", and "action_foreach" target types and
+  indicates the resulting files. Outputs must always refer to files in the
+  build directory.
+
+  copy
+    Copy targets should have exactly one entry in the outputs list. If there is
+    exactly one source, this can be a literal file name or a source expansion.
+    If there is more than one source, this must contain a source expansion to
+    map a single input name to a single output name. See "gn help copy".
+
+  action_foreach
+    Action_foreach targets must always use source expansions to map input files
+    to output files. There can be more than one output, which means that each
+    invocation of the script will produce a set of files (presumably based on
+    the name of the input file). See "gn help action_foreach".
+
+  action
+    Action targets (excluding action_foreach) must list literal output file(s)
+    with no source expansions. See "gn help action".
+```
+### <a name="var_partial_info_plist"></a>**partial_info_plist**: [filename] Path plist from asset catalog compiler.
+
+```
+  Valid for create_bundle target, corresponds to the path for the partial
+  Info.plist created by the asset catalog compiler that needs to be merged
+  with the application Info.plist (usually done by the code signing script).
+
+  The file will be generated regardless of whether the asset compiler has
+  been invoked or not. See "gn help create_bundle".
+```
+### <a name="var_pool"></a>**pool**: Label of the pool used by the action.
+
+```
+  A fully-qualified label representing the pool that will be used for the
+  action. Pools are defined using the pool() {...} declaration.
+```
+
+#### **Example**
+
+```
+  action("action") {
+    pool = "//build:custom_pool"
+    ...
+  }
+```
+### <a name="var_precompiled_header"></a>**precompiled_header**: [string] Header file to precompile.
+
+```
+  Precompiled headers will be used when a target specifies this value, or a
+  config applying to this target specifies this value. In addition, the tool
+  corresponding to the source files must also specify precompiled headers (see
+  "gn help tool"). The tool will also specify what type of precompiled headers
+  to use, by setting precompiled_header_type to either "gcc" or "msvc".
+
+  The precompiled header/source variables can be specified on a target or a
+  config, but must be the same for all configs applying to a given target since
+  a target can only have one precompiled header.
+
+  If you use both C and C++ sources, the precompiled header and source file
+  will be compiled once per language. You will want to make sure to wrap C++
+  includes in __cplusplus #ifdefs so the file will compile in C mode.
+```
+
+#### **GCC precompiled headers**
+
+```
+  When using GCC-style precompiled headers, "precompiled_source" contains the
+  path of a .h file that is precompiled and then included by all source files
+  in targets that set "precompiled_source".
+
+  The value of "precompiled_header" is not used with GCC-style precompiled
+  headers.
+```
+
+#### **MSVC precompiled headers**
+
+```
+  When using MSVC-style precompiled headers, the "precompiled_header" value is
+  a string corresponding to the header. This is NOT a path to a file that GN
+  recognises, but rather the exact string that appears in quotes after
+  an #include line in source code. The compiler will match this string against
+  includes or forced includes (/FI).
+
+  MSVC also requires a source file to compile the header with. This must be
+  specified by the "precompiled_source" value. In contrast to the header value,
+  this IS a GN-style file name, and tells GN which source file to compile to
+  make the .pch file used for subsequent compiles.
+
+  For example, if the toolchain specifies MSVC headers:
+
+    toolchain("vc_x64") {
+      ...
+      tool("cxx") {
+        precompiled_header_type = "msvc"
+        ...
+
+  You might make a config like this:
+
+    config("use_precompiled_headers") {
+      precompiled_header = "build/precompile.h"
+      precompiled_source = "//build/precompile.cc"
+
+      # Either your source files should #include "build/precompile.h"
+      # first, or you can do this to force-include the header.
+      cflags = [ "/FI$precompiled_header" ]
+    }
+
+  And then define a target that uses the config:
+
+    executable("doom_melon") {
+      configs += [ ":use_precompiled_headers" ]
+      ...
+```
+### <a name="var_precompiled_header_type"></a>**precompiled_header_type**: [string] "gcc" or "msvc".
+
+```
+  See "gn help precompiled_header".
+```
+### <a name="var_precompiled_source"></a>**precompiled_source**: [file name] Source file to precompile.
+
+```
+  The source file that goes along with the precompiled_header when using
+  "msvc"-style precompiled headers. It will be implicitly added to the sources
+  of the target. See "gn help precompiled_header".
+```
+### <a name="var_product_type"></a>**product_type**: Product type for Xcode projects.
+
+```
+  Correspond to the type of the product of a create_bundle target. Only
+  meaningful to Xcode (used as part of the Xcode project generation).
+
+  When generating Xcode project files, only create_bundle target with a
+  non-empty product_type will have a corresponding target in Xcode project.
+```
+### <a name="var_public"></a>**public**: Declare public header files for a target.
+
+```
+  A list of files that other targets can include. These permissions are checked
+  via the "check" command (see "gn help check").
+
+  If no public files are declared, other targets (assuming they have visibility
+  to depend on this target) can include any file in the sources list. If this
+  variable is defined on a target, dependent targets may only include files on
+  this whitelist unless that target is marked as a friend (see "gn help
+  friend").
+
+  Header file permissions are also subject to visibility. A target must be
+  visible to another target to include any files from it at all and the public
+  headers indicate which subset of those files are permitted. See "gn help
+  visibility" for more.
+
+  Public files are inherited through the dependency tree. So if there is a
+  dependency A -> B -> C, then A can include C's public headers. However, the
+  same is NOT true of visibility, so unless A is in C's visibility list, the
+  include will be rejected.
+
+  GN only knows about files declared in the "sources" and "public" sections of
+  targets. If a file is included that is not known to the build, it will be
+  allowed.
+
+  It is common for test targets to need to include private headers for their
+  associated code. In this case, list the test target in the "friend" list of
+  the target that owns the private header to allow the inclusion. See
+  "gn help friend" for more.
+
+  When a binary target has no explicit or implicit public headers (a "public"
+  list is defined but is empty), GN assumes that the target can not propagate
+  any compile-time dependencies up the dependency tree. In this case, the build
+  can be parallelized more efficiently.
+  Say there are dependencies:
+    A (shared library) -> B (shared library) -> C (action).
+  Normally C must complete before any source files in A can compile (because
+  there might be generated includes). But when B explicitly declares no public
+  headers, C can execute in parallel with A's compile steps. C must still be
+  complete before any dependents link.
+```
+
+#### **Examples**
+
+```
+  These exact files are public:
+    public = [ "foo.h", "bar.h" ]
+
+  No files are public (no targets may include headers from this one):
+    # This allows starting compilation in dependent targets earlier.
+    public = []
+```
+### <a name="var_public_configs"></a>**public_configs**: Configs to be applied on dependents.
+
+```
+  A list of config labels.
+
+  Targets directly depending on this one will have the configs listed in this
+  variable added to them. These configs will also apply to the current target.
+  Generally, public configs are used to apply defines and include directories
+  necessary to compile this target's header files.
+
+  See also "gn help all_dependent_configs".
+```
+
+#### **Propagation of public configs**
+
+```
+  Public configs are applied to all targets that depend directly on this one.
+  These dependent targets can further push this target's public configs
+  higher in the dependency tree by depending on it via public_deps (see "gn
+  help public_deps").
+
+    static_library("toplevel") {
+      # This target will get "my_config" applied to it. However, since this
+      # target uses "deps" and not "public_deps", targets that depend on this
+      # one won't get it.
+      deps = [ ":intermediate" ]
+    }
+
+    static_library("intermediate") {
+      # Depending on "lower" in any way will apply "my_config" to this target.
+      # Additionall, since this target depends on "lower" via public_deps,
+      # targets that depend on this one will also get "my_config".
+      public_deps = [ ":lower" ]
+    }
+
+    static_library("lower") {
+      # This will get applied to all targets that depend on this one.
+      public_configs = [ ":my_config" ]
+    }
+
+  Public config propagation happens in a second phase once a target and all of
+  its dependencies have been resolved. Therefore, a target will not see these
+  force-added configs in their "configs" variable while the script is running,
+  and they can not be removed. As a result, this capability should generally
+  only be used to add defines and include directories rather than setting
+  complicated flags that some targets may not want.
+
+  Public configs may or may not be propagated across toolchain boundaries
+  depending on the value of the propagates_configs flag (see "gn help
+  toolchain") on the toolchain of the target declaring the public_config.
+```
+
+#### **Avoiding applying public configs to this target**
+
+```
+  If you want the config to apply to targets that depend on this one, but NOT
+  this one, define an extra layer of indirection using a group:
+
+    # External targets depend on this group.
+    group("my_target") {
+      # Config to apply to all targets that depend on this one.
+      public_configs = [ ":external_settings" ]
+      deps = [ ":internal_target" ]
+    }
+
+    # Internal target to actually compile the sources.
+    static_library("internal_target") {
+      # Force all external targets to depend on the group instead of directly
+      # on this so the "external_settings" config will get applied.
+      visibility = [ ":my_target" ]
+      ...
+    }
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_public_deps"></a>**public_deps**: Declare public dependencies.
+
+```
+  Public dependencies are like private dependencies (see "gn help deps") but
+  additionally express that the current target exposes the listed deps as part
+  of its public API.
+
+  This has several ramifications:
+
+    - public_configs that are part of the dependency are forwarded to direct
+      dependents.
+
+    - Public headers in the dependency are usable by dependents (includes do
+      not require a direct dependency or visibility).
+
+    - If the current target is a shared library, other shared libraries that it
+      publicly depends on (directly or indirectly) are propagated up the
+      dependency tree to dependents for linking.
+
+  See also "gn help public_configs".
+```
+
+#### **Discussion**
+
+```
+  Say you have three targets: A -> B -> C. C's visibility may allow B to depend
+  on it but not A. Normally, this would prevent A from including any headers
+  from C, and C's public_configs would apply only to B.
+
+  If B lists C in its public_deps instead of regular deps, A will now inherit
+  C's public_configs and the ability to include C's public headers.
+
+  Generally if you are writing a target B and you include C's headers as part
+  of B's public headers, or targets depending on B should consider B and C to
+  be part of a unit, you should use public_deps instead of deps.
+```
+
+#### **Example**
+
+```
+  # This target can include files from "c" but not from
+  # "super_secret_implementation_details".
+  executable("a") {
+    deps = [ ":b" ]
+  }
+
+  shared_library("b") {
+    deps = [ ":super_secret_implementation_details" ]
+    public_deps = [ ":c" ]
+  }
+```
+### <a name="var_rebase"></a>**rebase**: Rebase collected metadata as files.
+
+```
+  A boolean that triggers a rebase of collected metadata strings based on their
+  declared file. Defaults to false.
+
+  Metadata generally declares files as strings relative to the local build file.
+  However, this data is often used in other contexts, and so setting this flag
+  will force the metadata collection to be rebased according to the local build
+  file's location and thus allow the filename to be used anywhere.
+
+  Setting this flag will raise an error if any target's specified metadata is
+  not a string value.
+
+  See also "gn help generated_file".
+```
+### <a name="var_response_file_contents"></a>**response_file_contents**: Contents of a response file for actions.
+
+```
+  Sometimes the arguments passed to a script can be too long for the system's
+  command-line capabilities. This is especially the case on Windows where the
+  maximum command-line length is less than 8K. A response file allows you to
+  pass an unlimited amount of data to a script in a temporary file for an
+  action or action_foreach target.
+
+  If the response_file_contents variable is defined and non-empty, the list
+  will be treated as script args (including possibly substitution patterns)
+  that will be written to a temporary file at build time. The name of the
+  temporary file will be substituted for "{{response_file_name}}" in the script
+  args.
+
+  The response file contents will always be quoted and escaped according to
+  Unix shell rules. To parse the response file, the Python script should use
+  "shlex.split(file_contents)".
+```
+
+#### **Example**
+
+```
+  action("process_lots_of_files") {
+    script = "process.py",
+    inputs = [ ... huge list of files ... ]
+
+    # Write all the inputs to a response file for the script. Also,
+    # make the paths relative to the script working directory.
+    response_file_contents = rebase_path(inputs, root_build_dir)
+
+    # The script expects the name of the response file in --file-list.
+    args = [
+      "--enable-foo",
+      "--file-list={{response_file_name}}",
+    ]
+  }
+```
+### <a name="var_script"></a>**script**: Script file for actions.
+
+```
+  An absolute or buildfile-relative file name of a Python script to run for a
+  action and action_foreach targets (see "gn help action" and "gn help
+  action_foreach").
+```
+### <a name="var_sources"></a>**sources**: Source files for a target
+
+```
+  A list of files. Non-absolute paths will be resolved relative to the current
+  build file.
+```
+
+#### **Sources for binary targets**
+
+```
+  For binary targets (source sets, executables, and libraries), the known file
+  types will be compiled with the associated tools. Unknown file types and
+  headers will be skipped. However, you should still list all C/C+ header files
+  so GN knows about the existence of those files for the purposes of include
+  checking.
+
+  As a special case, a file ending in ".def" will be treated as a Windows
+  module definition file. It will be appended to the link line with a
+  preceding "/DEF:" string. There must be at most one .def file in a target
+  and they do not cross dependency boundaries (so specifying a .def file in a
+  static library or source set will have no effect on the executable or shared
+  library they're linked into).
+
+  For Rust targets that do not specify a crate_root, then the crate_root will
+  look for a lib.rs file (or main.rs for executable) or a single file in
+  sources, if sources contains only one file.
+```
+
+#### **Sources for non-binary targets**
+
+```
+  action_foreach
+    The sources are the set of files that the script will be executed over. The
+    script will run once per file.
+
+  action
+    The sources will be treated the same as inputs. See "gn help inputs" for
+    more information and usage advice.
+
+  copy
+    The source are the source files to copy.
+```
+### <a name="var_swiftflags"></a>**swiftflags**: Flags passed to the swift compiler.
+
+```
+  A list of strings.
+
+  "swiftflags" are passed to any invocation of a tool that takes an .swift
+  file as input.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+### <a name="var_testonly"></a>**testonly**: Declares a target must only be used for testing.
+
+```
+  Boolean. Defaults to false.
+
+  When a target is marked "testonly = true", it must only be depended on by
+  other test-only targets. Otherwise, GN will issue an error that the
+  depenedency is not allowed.
+
+  This feature is intended to prevent accidentally shipping test code in a
+  final product.
+```
+
+#### **Example**
+
+```
+  source_set("test_support") {
+    testonly = true
+    ...
+  }
+```
+### <a name="var_visibility"></a>**visibility**: A list of labels that can depend on a target.
+
+```
+  A list of labels and label patterns that define which targets can depend on
+  the current one. These permissions are checked via the "check" command (see
+  "gn help check").
+
+  If visibility is not defined, it defaults to public ("*").
+
+  If visibility is defined, only the targets with labels that match it can
+  depend on the current target. The empty list means no targets can depend on
+  the current target.
+
+  Tip: Often you will want the same visibility for all targets in a BUILD file.
+  In this case you can just put the definition at the top, outside of any
+  target, and the targets will inherit that scope and see the definition.
+```
+
+#### **Patterns**
+
+```
+  See "gn help label_pattern" for more details on what types of patterns are
+  supported. If a toolchain is specified, only targets in that toolchain will
+  be matched. If a toolchain is not specified on a pattern, targets in all
+  toolchains will be matched.
+```
+
+#### **Examples**
+
+```
+  Only targets in the current buildfile ("private"):
+    visibility = [ ":*" ]
+
+  No targets (used for targets that should be leaf nodes):
+    visibility = []
+
+  Any target ("public", the default):
+    visibility = [ "*" ]
+
+  All targets in the current directory and any subdirectory:
+    visibility = [ "./*" ]
+
+  Any target in "//bar/BUILD.gn":
+    visibility = [ "//bar:*" ]
+
+  Any target in "//bar/" or any subdirectory thereof:
+    visibility = [ "//bar/*" ]
+
+  Just these specific targets:
+    visibility = [ ":mything", "//foo:something_else" ]
+
+  Any target in the current directory and any subdirectory thereof, plus
+  any targets in "//bar/" and any subdirectory thereof.
+    visibility = [ "./*", "//bar/*" ]
+```
+### <a name="var_walk_keys"></a>**walk_keys**: Key(s) for managing the metadata collection walk.
+
+```
+  Defaults to [""].
+
+  These keys are used to control the next step in a collection walk, acting as
+  barriers. If a specified key is defined in a target's metadata, the walk will
+  use the targets listed in that value to determine which targets are walked.
+
+  If no walk_keys are specified for a generated_file target (i.e. "[""]"), the
+  walk will touch all deps and data_deps of the specified target recursively.
+
+  See "gn help generated_file".
+```
+### <a name="var_weak_frameworks"></a>**weak_frameworks**: [name list] Name of frameworks that must be weak linked.
+
+```
+  A list of framework names.
+
+  The frameworks named in that list will be weak linked with any dynamic link
+  type target. Weak linking instructs the dynamic loader to attempt to load
+  the framework, but if it is not able to do so, it leaves any imported symbols
+  unresolved. This is typically used when a framework is present in a new
+  version of an SDK but not on older versions of the OS that the software runs
+  on.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurrence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
+```
+### <a name="var_write_runtime_deps"></a>**write_runtime_deps**: Writes the target's runtime_deps to the given path.
+
+```
+  Does not synchronously write the file, but rather schedules it to be written
+  at the end of generation.
+
+  If the file exists and the contents are identical to that being written, the
+  file will not be updated. This will prevent unnecessary rebuilds of targets
+  that depend on this file.
+
+  Path must be within the output directory.
+
+  See "gn help runtime_deps" for how the runtime dependencies are computed.
+
+  The format of this file will list one file per line with no escaping. The
+  files will be relative to the root_build_dir. The first line of the file will
+  be the main output file of the target itself. The file contents will be the
+  same as requesting the runtime deps be written on the command line (see "gn
+  help --runtime-deps-list-file").
+```
+### <a name="var_xcasset_compiler_flags"></a>**xcasset_compiler_flags**: Flags passed to xcassets compiler.
+
+```
+  A list of strings.
+
+  Valid for create_bundle target. Those flags are directly passed to
+  xcassets compiler, corresponding to {{xcasset_compiler_flags}} substitution
+  in compile_xcassets tool.
+```
+### <a name="var_xcode_extra_attributes"></a>**xcode_extra_attributes**: [scope] Extra attributes for Xcode projects.
+
+```
+  The value defined in this scope will be copied to the EXTRA_ATTRIBUTES
+  property of the generated Xcode project. They are only meaningful when
+  generating with --ide=xcode.
+
+  See "gn help create_bundle" for more information.
+```
+### <a name="var_xcode_test_application_name"></a>**xcode_test_application_name**: Name for Xcode test target.
+
+```
+  Each unit and ui test target must have a test application target, and this
+  value is used to specify the relationship. Only meaningful to Xcode (used as
+  part of the Xcode project generation).
+
+  See "gn help create_bundle" for more information.
+```
+
+#### **Example**
+
+```
+  create_bundle("chrome_xctest") {
+    test_application_name = "chrome"
+    ...
+  }
+```
+## <a name="other"></a>Other help topics
+
+### <a name="buildargs"></a>**Build Arguments Overview**
+
+```
+  Build arguments are variables passed in from outside of the build that build
+  files can query to determine how the build works.
+```
+
+#### **How build arguments are set**
+
+```
+  First, system default arguments are set based on the current system. The
+  built-in arguments are:
+   - host_cpu
+   - host_os
+   - current_cpu
+   - current_os
+   - target_cpu
+   - target_os
+
+  Next, project-specific overrides are applied. These are specified inside
+  the default_args variable of //.gn. See "gn help dotfile" for more.
+
+  If specified, arguments from the --args command line flag are used. If that
+  flag is not specified, args from previous builds in the build directory will
+  be used (this is in the file args.gn in the build directory).
+
+  Last, for targets being compiled with a non-default toolchain, the toolchain
+  overrides are applied. These are specified in the toolchain_args section of a
+  toolchain definition. The use-case for this is that a toolchain may be
+  building code for a different platform, and that it may want to always
+  specify Posix, for example. See "gn help toolchain" for more.
+
+  If you specify an override for a build argument that never appears in a
+  "declare_args" call, a nonfatal error will be displayed.
+```
+
+#### **Examples**
+
+```
+  gn args out/FooBar
+      Create the directory out/FooBar and open an editor. You would type
+      something like this into that file:
+          enable_doom_melon=false
+          os="android"
+
+  gn gen out/FooBar --args="enable_doom_melon=true os=\"android\""
+      This will overwrite the build directory with the given arguments. (Note
+      that the quotes inside the args command will usually need to be escaped
+      for your shell to pass through strings values.)
+```
+
+#### **How build arguments are used**
+
+```
+  If you want to use an argument, you use declare_args() and specify default
+  values. These default values will apply if none of the steps listed in the
+  "How build arguments are set" section above apply to the given argument, but
+  the defaults will not override any of these.
+
+  Often, the root build config file will declare global arguments that will be
+  passed to all buildfiles. Individual build files can also specify arguments
+  that apply only to those files. It is also useful to specify build args in an
+  "import"-ed file if you want such arguments to apply to multiple buildfiles.
+```
+### <a name="dotfile"></a>**.gn file**
+
+```
+  When gn starts, it will search the current directory and parent directories
+  for a file called ".gn". This indicates the source root. You can override
+  this detection by using the --root command-line argument
+
+  The .gn file in the source root will be executed. The syntax is the same as a
+  buildfile, but with very limited build setup-specific meaning.
+
+  If you specify --root, by default GN will look for the file .gn in that
+  directory. If you want to specify a different file, you can additionally pass
+  --dotfile:
+
+    gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn
+```
+
+#### **Variables**
+
+```
+  arg_file_template [optional]
+      Path to a file containing the text that should be used as the default
+      args.gn content when you run `gn args`.
+
+  buildconfig [required]
+      Path to the build config file. This file will be used to set up the
+      build file execution environment for each toolchain.
+
+  check_targets [optional]
+      A list of labels and label patterns that should be checked when running
+      "gn check" or "gn gen --check". If neither check_targets or
+      no_check_targets (see below) is specified, all targets will be checked.
+      It is an error to specify both check_targets and no_check_targets. If it
+      is the empty list, no targets will be checked. To bypass this list,
+      request an explicit check of targets, like "//*".
+
+      The format of this list is identical to that of "visibility" so see "gn
+      help visibility" for examples.
+
+  no_check_targets [optional]
+      A list of labels and label patterns that should *not* be checked when
+      running "gn check" or "gn gen --check". All other targets will be checked.
+      If neither check_targets (see above) or no_check_targets is specified, all
+      targets will be checked. It is an error to specify both check_targets and
+      no_check_targets.
+
+      The format of this list is identical to that of "visibility" so see "gn
+      help visibility" for examples.
+
+  check_system_includes [optional]
+      Boolean to control whether system style includes are checked by default
+      when running "gn check" or "gn gen --check".  System style includes are
+      includes that use angle brackets <> instead of double quotes "". If this
+      setting is omitted or set to false, these includes will be ignored by
+      default. They can be checked explicitly by running
+      "gn check --check-system" or "gn gen --check=system"
+
+  exec_script_whitelist [optional]
+      A list of .gn/.gni files (not labels) that have permission to call the
+      exec_script function. If this list is defined, calls to exec_script will
+      be checked against this list and GN will fail if the current file isn't
+      in the list.
+
+      This is to allow the use of exec_script to be restricted since is easy to
+      use inappropriately. Wildcards are not supported. Files in the
+      secondary_source tree (if defined) should be referenced by ignoring the
+      secondary tree and naming them as if they are in the main tree.
+
+      If unspecified, the ability to call exec_script is unrestricted.
+
+      Example:
+        exec_script_whitelist = [
+          "//base/BUILD.gn",
+          "//build/my_config.gni",
+        ]
+
+  root [optional]
+      Label of the root build target. The GN build will start by loading the
+      build file containing this target name. This defaults to "//:" which will
+      cause the file //BUILD.gn to be loaded. Note that build_file_extension
+      applies to the default case as well.
+
+      The command-line switch --root-target will override this value (see "gn
+      help --root-target").
+
+  script_executable [optional]
+      Path to specific Python executable or other interpreter to use in
+      action targets and exec_script calls. By default GN searches the
+      PATH for Python to execute these scripts.
+
+      If set to the empty string, the path specified in action targets
+      and exec_script calls will be executed directly.
+
+  secondary_source [optional]
+      Label of an alternate directory tree to find input files. When searching
+      for a BUILD.gn file (or the build config file discussed above), the file
+      will first be looked for in the source root. If it's not found, the
+      secondary source root will be checked (which would contain a parallel
+      directory hierarchy).
+
+      This behavior is intended to be used when BUILD.gn files can't be checked
+      in to certain source directories for whatever reason.
+
+      The secondary source root must be inside the main source tree.
+
+  default_args [optional]
+      Scope containing the default overrides for declared arguments. These
+      overrides take precedence over the default values specified in the
+      declare_args() block, but can be overridden using --args or the
+      args.gn file.
+
+      This is intended to be used when subprojects declare arguments with
+      default values that need to be changed for whatever reason.
+
+  build_file_extension [optional]
+      If set to a non-empty string, this is added to the name of all build files
+      to load.
+      GN will look for build files named "BUILD.$build_file_extension.gn".
+      This is intended to be used during migrations or other situations where
+      there are two independent GN builds in the same directories.
+
+  ninja_required_version [optional]
+      When set specifies the minimum required version of Ninja. The default
+      required version is 1.7.2. Specifying a higher version might enable the
+      use of some of newer features that can make the build more efficient.
+```
+
+#### **Example .gn file contents**
+
+```
+  buildconfig = "//build/config/BUILDCONFIG.gn"
+
+  check_targets = [
+    "//doom_melon/*",  # Check everything in this subtree.
+    "//tools:mind_controlling_ant",  # Check this specific target.
+  ]
+
+  root = "//:root"
+
+  secondary_source = "//build/config/temporary_buildfiles/"
+
+  default_args = {
+    # Default to release builds for this project.
+    is_debug = false
+    is_component_build = false
+  }
+```
+### <a name="execution"></a>**Build graph and execution overview**
+
+#### **Overall build flow**
+
+```
+  1. Look for ".gn" file (see "gn help dotfile") in the current directory and
+     walk up the directory tree until one is found. Set this directory to be
+     the "source root" and interpret this file to find the name of the build
+     config file.
+
+  2. Execute the build config file identified by .gn to set up the global
+     variables and default toolchain name. Any arguments, variables, defaults,
+     etc. set up in this file will be visible to all files in the build.
+
+  3. Load the //BUILD.gn (in the source root directory).
+
+  4. Recursively evaluate rules and load BUILD.gn in other directories as
+     necessary to resolve dependencies. If a BUILD file isn't found in the
+     specified location, GN will look in the corresponding location inside
+     the secondary_source defined in the dotfile (see "gn help dotfile").
+
+  5. When a target's dependencies are resolved, write out the `.ninja`
+     file to disk.
+
+  6. When all targets are resolved, write out the root build.ninja file.
+
+  Note that the BUILD.gn file name may be modulated by .gn arguments such as
+  build_file_extension.
+```
+
+#### **Executing target definitions and templates**
+
+```
+  Build files are loaded in parallel. This means it is impossible to
+  interrogate a target from GN code for any information not derivable from its
+  label (see "gn help label"). The exception is the get_target_outputs()
+  function which requires the target being interrogated to have been defined
+  previously in the same file.
+
+  Targets are declared by their type and given a name:
+
+    static_library("my_static_library") {
+      ... target parameter definitions ...
+    }
+
+  There is also a generic "target" function for programmatically defined types
+  (see "gn help target"). You can define new types using templates (see "gn
+  help template"). A template defines some custom code that expands to one or
+  more other targets.
+
+  Before executing the code inside the target's { }, the target defaults are
+  applied (see "gn help set_defaults"). It will inject implicit variable
+  definitions that can be overridden by the target code as necessary. Typically
+  this mechanism is used to inject a default set of configs that define the
+  global compiler and linker flags.
+```
+
+#### **Which targets are built**
+
+```
+  All targets encountered in the default toolchain (see "gn help toolchain")
+  will have build rules generated for them, even if no other targets reference
+  them. Their dependencies must resolve and they will be added to the implicit
+  "all" rule (see "gn help ninja_rules").
+
+  Targets in non-default toolchains will only be generated when they are
+  required (directly or transitively) to build a target in the default
+  toolchain.
+
+  See also "gn help ninja_rules".
+```
+
+#### **Dependencies**
+
+```
+  The only difference between "public_deps" and "deps" except for pushing
+  configs around the build tree and allowing includes for the purposes of "gn
+  check".
+
+  A target's "data_deps" are guaranteed to be built whenever the target is
+  built, but the ordering is not defined. The meaning of this is dependencies
+  required at runtime. Currently data deps will be complete before the target
+  is linked, but this is not semantically guaranteed and this is undesirable
+  from a build performance perspective. Since we hope to change this in the
+  future, do not rely on this behavior.
+```
+### <a name="grammar"></a>**Language and grammar for GN build files**
+
+#### **Tokens**
+
+```
+  GN build files are read as sequences of tokens.  While splitting the file
+  into tokens, the next token is the longest sequence of characters that form a
+  valid token.
+```
+
+#### **White space and comments**
+
+```
+  White space is comprised of spaces (U+0020), horizontal tabs (U+0009),
+  carriage returns (U+000D), and newlines (U+000A).
+
+  Comments start at the character "#" and stop at the next newline.
+
+  White space and comments are ignored except that they may separate tokens
+  that would otherwise combine into a single token.
+```
+
+#### **Identifiers**
+
+```
+  Identifiers name variables and functions.
+
+      identifier = letter { letter | digit } .
+      letter     = "A" ... "Z" | "a" ... "z" | "_" .
+      digit      = "0" ... "9" .
+```
+
+#### **Keywords**
+
+```
+  The following keywords are reserved and may not be used as identifiers:
+
+          else    false   if      true
+```
+
+#### **Integer literals**
+
+```
+  An integer literal represents a decimal integer value.
+
+      integer = [ "-" ] digit { digit } .
+
+  Leading zeros and negative zero are disallowed.
+```
+
+#### **String literals**
+
+```
+  A string literal represents a string value consisting of the quoted
+  characters with possible escape sequences and variable expansions.
+
+      string           = `"` { char | escape | expansion } `"` .
+      escape           = `\` ( "$" | `"` | char ) .
+      BracketExpansion = "{" ( identifier | ArrayAccess | ScopeAccess ) "}" .
+      Hex              = "0x" [0-9A-Fa-f][0-9A-Fa-f]
+      expansion        = "$" ( identifier | BracketExpansion | Hex ) .
+      char             = /* any character except "$", `"`, or newline */ .
+
+  After a backslash, certain sequences represent special characters:
+
+          \"    U+0022    quotation mark
+          \$    U+0024    dollar sign
+          \\    U+005C    backslash
+
+  All other backslashes represent themselves.
+
+  To insert an arbitrary byte value, use $0xFF. For example, to insert a
+  newline character: "Line one$0x0ALine two".
+
+  An expansion will evaluate the variable following the '$' and insert a
+  stringified version of it into the result. For example, to concat two path
+  components with a slash separating them:
+    "$var_one/$var_two"
+  Use the "${var_one}" format to be explicitly deliniate the variable for
+  otherwise-ambiguous cases.
+```
+
+#### **Punctuation**
+
+```
+  The following character sequences represent punctuation:
+
+          +       +=      ==      !=      (       )
+          -       -=      <       <=      [       ]
+          !       =       >       >=      {       }
+                          &&      ||      .       ,
+```
+
+#### **Grammar**
+
+```
+  The input tokens form a syntax tree following a context-free grammar:
+
+      File = StatementList .
+
+      Statement     = Assignment | Call | Condition .
+      LValue        = identifier | ArrayAccess | ScopeAccess .
+      Assignment    = LValue AssignOp Expr .
+      Call          = identifier "(" [ ExprList ] ")" [ Block ] .
+      Condition     = "if" "(" Expr ")" Block
+                      [ "else" ( Condition | Block ) ] .
+      Block         = "{" StatementList "}" .
+      StatementList = { Statement } .
+
+      ArrayAccess = identifier "[" Expr "]" .
+      ScopeAccess = identifier "." identifier .
+      Expr        = UnaryExpr | Expr BinaryOp Expr .
+      UnaryExpr   = PrimaryExpr | UnaryOp UnaryExpr .
+      PrimaryExpr = identifier | integer | string | Call
+                  | ArrayAccess | ScopeAccess | Block
+                  | "(" Expr ")"
+                  | "[" [ ExprList [ "," ] ] "]" .
+      ExprList    = Expr { "," Expr } .
+
+      AssignOp = "=" | "+=" | "-=" .
+      UnaryOp  = "!" .
+      BinaryOp = "+" | "-"                  // highest priority
+               | "<" | "<=" | ">" | ">="
+               | "==" | "!="
+               | "&&"
+               | "||" .                     // lowest priority
+
+  All binary operators are left-associative.
+```
+
+#### **Types**
+
+```
+  The GN language is dynamically typed. The following types are used:
+
+   - Boolean: Uses the keywords "true" and "false". There is no implicit
+     conversion between booleans and integers.
+
+   - Integers: All numbers in GN are signed 64-bit integers.
+
+   - Strings: Strings are 8-bit with no enforced encoding. When a string is
+     used to interact with other systems with particular encodings (like the
+     Windows and Mac filesystems) it is assumed to be UTF-8. See "String
+     literals" above for more.
+
+   - Lists: Lists are arbitrary-length ordered lists of values. See "Lists"
+     below for more.
+
+   - Scopes: Scopes are like dictionaries that use variable names for keys. See
+     "Scopes" below for more.
+```
+
+#### **Lists**
+
+```
+  Lists are created with [] and using commas to separate items:
+
+       mylist = [ 0, 1, 2, "some string" ]
+
+  A comma after the last item is optional. Lists are dereferenced using 0-based
+  indexing:
+
+       mylist[0] += 1
+       var = mylist[2]
+
+  Lists can be concatenated using the '+' and '+=' operators. Bare values can
+  not be concatenated with lists, to add a single item, it must be put into a
+  list of length one.
+
+  Items can be removed from lists using the '-' and '-=' operators. This will
+  remove all occurrences of every item in the right-hand list from the
+  left-hand list. It is an error to remove an item not in the list. This is to
+  prevent common typos and to detect dead code that is removing things that no
+  longer apply.
+
+  It is an error to use '=' to replace a nonempty list with another nonempty
+  list. This is to prevent accidentally overwriting data when in most cases
+  '+=' was intended. To overwrite a list on purpose, first assign it to the
+  empty list:
+
+    mylist = []
+    mylist = otherlist
+```
+
+#### **Scopes**
+
+```
+  All execution happens in the context of a scope which holds the current state
+  (like variables). With the exception of loops and conditions, '{' introduces
+  a new scope that has a parent reference to the old scope.
+
+  Variable reads recursively search all nested scopes until the variable is
+  found or there are no more scopes. Variable writes always go into the current
+  scope. This means that after the closing '}' (again excepting loops and
+  conditions), all local variables will be restored to the previous values.
+  This also means that "foo = foo" can do useful work by copying a variable
+  into the current scope that was defined in a containing scope.
+
+  Scopes can also be assigned to variables. Such scopes can be created by
+  functions like exec_script, when invoking a template (the template code
+  refers to the variables set by the invoking code by the implicitly-created
+  "invoker" scope), or explicitly like:
+
+    empty_scope = {}
+    myvalues = {
+      foo = 21
+      bar = "something"
+    }
+
+  Inside such a scope definition can be any GN code including conditionals and
+  function calls. After the close of the scope, it will contain all variables
+  explicitly set by the code contained inside it. After this, the values can be
+  read, modified, or added to:
+
+    myvalues.foo += 2
+    empty_scope.new_thing = [ 1, 2, 3 ]
+
+  Scope equality is defined as single-level scopes identical within the current
+  scope. That is, all values in the first scope must be present and identical
+  within the second, and vice versa. Note that this means inherited scopes are
+  always unequal by definition.
+```
+### <a name="io_conversion"></a>**Input and output conversion**
+
+```
+  Input and output conversions are arguments to file and process functions
+  that specify how to convert data to or from external formats. The possible
+  values for parameters specifying conversions are:
+
+  "" (the default)
+      input: Discard the result and return None.
+
+      output: If value is a list, then "list lines"; otherwise "value".
+
+  "list lines"
+      input:
+        Return the file contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line may or may
+        not end in a newline.
+
+        After splitting, each individual line will be trimmed of whitespace on
+        both ends.
+
+      output:
+        Renders the value contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line will end in
+        with a newline.
+
+  "scope"
+      input:
+        Execute the block as GN code and return a scope with the resulting
+        values in it. If the input was:
+          a = [ "hello.cc", "world.cc" ]
+          b = 26
+        and you read the result into a variable named "val", then you could
+        access contents the "." operator on "val":
+          sources = val.a
+          some_count = val.b
+
+      output:
+        Renders the value contents as a GN code block, reversing the input
+        result above.
+
+  "string"
+      input: Return the file contents into a single string.
+
+      output:
+        Render the value contents into a single string. The output is:
+        a string renders with quotes, e.g. "str"
+        an integer renders as a stringified integer, e.g. "6"
+        a boolean renders as the associated string, e.g. "true"
+        a list renders as a representation of its contents, e.g. "[\"str\", 6]"
+        a scope renders as a GN code block of its values. If the Value was:
+            Value val;
+            val.a = [ "hello.cc", "world.cc" ];
+            val.b = 26
+          the resulting output would be:
+            "{
+                a = [ \"hello.cc\", \"world.cc\" ]
+                b = 26
+            }"
+
+  "value"
+      input:
+        Parse the input as if it was a literal rvalue in a buildfile. Examples of
+        typical program output using this mode:
+          [ "foo", "bar" ]     (result will be a list)
+        or
+          "foo bar"            (result will be a string)
+        or
+          5                    (result will be an integer)
+
+        Note that if the input is empty, the result will be a null value which
+        will produce an error if assigned to a variable.
+
+      output:
+        Render the value contents as a literal rvalue. Strings render with
+        escaped quotes.
+
+  "json"
+      input: Parse the input as a JSON and convert it to equivalent GN rvalue.
+
+      output: Convert the Value to equivalent JSON value.
+
+      The data type mapping is:
+        a string in JSON maps to string in GN
+        an integer in JSON maps to integer in GN
+        a float in JSON is unsupported and will result in an error
+        an object in JSON maps to scope in GN
+        an array in JSON maps to list in GN
+        a boolean in JSON maps to boolean in GN
+        a null in JSON is unsupported and will result in an error
+
+      Nota that the input dictionary keys have to be valid GN identifiers
+      otherwise they will produce an error.
+
+  "trim ..." (input only)
+      Prefixing any of the other transformations with the word "trim" will
+      result in whitespace being trimmed from the beginning and end of the
+      result before processing.
+
+      Examples: "trim string" or "trim list lines"
+
+      Note that "trim value" is useless because the value parser skips
+      whitespace anyway.
+```
+### <a name="file_pattern"></a>**File patterns**
+
+```
+  File patterns are VERY limited regular expressions. They must match the
+  entire input string to be counted as a match. In regular expression parlance,
+  there is an implicit "^...$" surrounding your input. If you want to match a
+  substring, you need to use wildcards at the beginning and end.
+
+  There are only two special tokens understood by the pattern matcher.
+  Everything else is a literal.
+
+   - "*" Matches zero or more of any character. It does not depend on the
+     preceding character (in regular expression parlance it is equivalent to
+     ".*").
+
+   - "\b" Matches a path boundary. This will match the beginning or end of a
+     string, or a slash.
+```
+
+#### **Pattern examples**
+
+```
+  "*asdf*"
+      Matches a string containing "asdf" anywhere.
+
+  "asdf"
+      Matches only the exact string "asdf".
+
+  "*.cc"
+      Matches strings ending in the literal ".cc".
+
+  "\bwin/*"
+      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+```
+### <a name="label_pattern"></a>**Label patterns**
+
+```
+  A label pattern is a way of expressing one or more labels in a portion of the
+  source tree. They are not general regular expressions.
+
+  They can take the following forms only:
+
+   - Explicit (no wildcard):
+       "//foo/bar:baz"
+       ":baz"
+
+   - Wildcard target names:
+       "//foo/bar:*" (all targets in the //foo/bar/BUILD.gn file)
+       ":*"  (all targets in the current build file)
+
+   - Wildcard directory names ("*" is only supported at the end)
+       "*"  (all targets)
+       "//foo/bar/*"  (all targets in any subdir of //foo/bar)
+       "./*"  (all targets in the current build file or sub dirs)
+
+  Any of the above forms can additionally take an explicit toolchain
+  in parenthesis at the end of the label pattern. In this case, the
+  toolchain must be fully qualified (no wildcards are supported in the
+  toolchain name).
+
+    "//foo:bar(//build/toolchain:mac)"
+        An explicit target in an explicit toolchain.
+
+    ":*(//build/toolchain/linux:32bit)"
+        All targets in the current build file using the 32-bit Linux toolchain.
+
+    "//foo/*(//build/toolchain:win)"
+        All targets in //foo and any subdirectory using the Windows
+        toolchain.
+```
+### <a name="labels"></a>**About labels**
+
+```
+  Everything that can participate in the dependency graph (targets, configs,
+  and toolchains) are identified by labels. A common label looks like:
+
+    //base/test:test_support
+
+  This consists of a source-root-absolute path, a colon, and a name. This means
+  to look for the thing named "test_support" in "base/test/BUILD.gn".
+
+  You can also specify system absolute paths if necessary. Typically such
+  paths would be specified via a build arg so the developer can specify where
+  the component is on their system.
+
+    /usr/local/foo:bar    (Posix)
+    /C:/Program Files/MyLibs:bar   (Windows)
+```
+
+#### **Toolchains**
+
+```
+  A canonical label includes the label of the toolchain being used. Normally,
+  the toolchain label is implicitly inherited from the current execution
+  context, but you can override this to specify cross-toolchain dependencies:
+
+    //base/test:test_support(//build/toolchain/win:msvc)
+
+  Here GN will look for the toolchain definition called "msvc" in the file
+  "//build/toolchain/win" to know how to compile this target.
+```
+
+#### **Relative labels**
+
+```
+  If you want to refer to something in the same buildfile, you can omit
+  the path name and just start with a colon. This format is recommended for
+  all same-file references.
+
+    :base
+
+  Labels can be specified as being relative to the current directory.
+  Stylistically, we prefer to use absolute paths for all non-file-local
+  references unless a build file needs to be run in different contexts (like a
+  project needs to be both standalone and pulled into other projects in
+  difference places in the directory hierarchy).
+
+    source/plugin:myplugin
+    ../net:url_request
+```
+
+#### **Implicit names**
+
+```
+  If a name is unspecified, it will inherit the directory name. Stylistically,
+  we prefer to omit the colon and name when possible:
+
+    //net  ->  //net:net
+    //tools/gn  ->  //tools/gn:gn
+```
+### <a name="metadata_collection"></a>**Metadata Collection**
+
+```
+  Metadata is information attached to targets throughout the dependency tree. GN
+  allows for the collection of this data into files written during the generation
+  step, enabling users to expose and aggregate this data based on the dependency
+  tree.
+```
+
+#### **generated_file targets**
+
+```
+  Similar to the write_file() function, the generated_file target type
+  creates a file in the specified location with the specified content. The
+  primary difference between write_file() and this target type is that the
+  write_file function does the file write at parse time, while the
+  generated_file target type writes at target resolution time. See
+  "gn help generated_file" for more detail.
+
+  When written at target resolution time, generated_file enables GN to
+  collect and write aggregated metadata from dependents.
+
+  A generated_file target can declare either 'contents' to write statically
+  known contents to a file or 'data_keys' to aggregate metadata and write the
+  result to a file. It can also specify 'walk_keys' (to restrict the metadata
+  collection), 'output_conversion', and 'rebase'.
+```
+
+#### **Collection and Aggregation**
+
+```
+  Targets can declare a 'metadata' variable containing a scope, and this
+  metadata may be collected and written out to a file specified by
+  generated_file aggregation targets. The 'metadata' scope must contain
+  only list values since the aggregation step collects a list of these values.
+
+  During the target resolution, generated_file targets will walk their
+  dependencies recursively, collecting metadata based on the specified
+  'data_keys'. 'data_keys' is specified as a list of strings, used by the walk
+  to identify which variables in dependencies' 'metadata' scopes to collect.
+
+  The walk begins with the listed dependencies of the 'generated_file' target.
+  The 'metadata' scope for each dependency is inspected for matching elements
+  of the 'generated_file' target's 'data_keys' list.  If a match is found, the
+  data from the dependent's matching key list is appended to the aggregate walk
+  list. Note that this means that if more than one walk key is specified, the
+  data in all of them will be aggregated into one list. From there, the walk
+  will then recurse into the dependencies of each target it encounters,
+  collecting the specified metadata for each.
+
+  For example:
+
+    group("a") {
+      metadata = {
+        doom_melon = [ "enable" ]
+        my_files = [ "foo.cpp" ]
+        my_extra_files = [ "bar.cpp" ]
+      }
+
+      deps = [ ":b" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "baz.cpp" ]
+      }
+    }
+
+    generated_file("metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files", "my_extra_files" ]
+
+      deps = [ ":a" ]
+    }
+
+  The above will produce the following file data:
+
+    foo.cpp
+    bar.cpp
+    baz.cpp
+
+  The dependency walk can be limited by using the 'walk_keys'. This is a list of
+  labels that should be included in the walk. All labels specified here should
+  also be in one of the deps lists. These keys act as barriers, where the walk
+  will only recurse into the targets listed. An empty list in all specified
+  barriers will end that portion of the walk.
+
+    group("a") {
+      metadata = {
+        my_files = [ "foo.cpp" ]
+        my_files_barrier = [ ":b" ]
+      }
+
+      deps = [ ":b", ":c" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "bar.cpp" ]
+      }
+    }
+
+    group("c") {
+      metadata = {
+        my_files = [ "doom_melon.cpp" ]
+      }
+    }
+
+    generated_file("metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files" ]
+      walk_keys = [ "my_files_barrier" ]
+
+      deps = [ ":a" ]
+    }
+
+  The above will produce the following file data (note that `doom_melon.cpp` is
+  not included):
+
+    foo.cpp
+    bar.cpp
+
+  A common example of this sort of barrier is in builds that have host tools
+  built as part of the tree, but do not want the metadata from those host tools
+  to be collected with the target-side code.
+```
+
+#### **Common Uses**
+
+```
+  Metadata can be used to collect information about the different targets in the
+  build, and so a common use is to provide post-build tooling with a set of data
+  necessary to do aggregation tasks. For example, if each test target specifies
+  the output location of its binary to run in a metadata field, that can be
+  collected into a single file listing the locations of all tests in the
+  dependency tree. A local build tool (or continuous integration infrastructure)
+  can then use that file to know which tests exist, and where, and run them
+  accordingly.
+
+  Another use is in image creation, where a post-build image tool needs to know
+  various pieces of information about the components it should include in order
+  to put together the correct image.
+```
+### <a name="ninja_rules"></a>**Ninja build rules**
+
+#### **The "all" and "default" rules**
+
+```
+  All generated targets (see "gn help execution") will be added to an implicit
+  build rule called "all" so "ninja all" will always compile everything. The
+  default rule will be used by Ninja if no specific target is specified (just
+  typing "ninja"). If there is a target named "default" in the root build file,
+  it will be the default build rule, otherwise the implicit "all" rule will be
+  used.
+```
+
+#### **Phony rules**
+
+```
+  GN generates Ninja "phony" rules for targets in the default toolchain.  The
+  phony rules can collide with each other and with the names of generated files
+  so are generated with the following priority:
+
+    1. Actual files generated by the build always take precedence.
+
+    2. Targets in the toplevel //BUILD.gn file.
+
+    3. Targets in toplevel directories matching the names of the directories.
+       So "ninja foo" can be used to compile "//foo:foo". This only applies to
+       the first level of directories since usually these are the most
+       important (so this won't apply to "//foo/bar:bar").
+
+    4. The short names of executables if there is only one executable with that
+       short name. Use "ninja doom_melon" to compile the
+       "//tools/fruit:doom_melon" executable.
+
+    5. The short names of all targets if there is only one target with that
+       short name.
+
+    6. Full label name with no leading slashes. So you can use
+       "ninja tools/fruit:doom_melon" to build "//tools/fruit:doom_melon".
+
+    7. Labels with an implicit name part (when the short names match the
+       directory). So you can use "ninja foo/bar" to compile "//foo/bar:bar".
+
+  These "phony" rules are provided only for running Ninja since this matches
+  people's historical expectations for building. For consistency with the rest
+  of the program, GN introspection commands accept explicit labels.
+
+  To explicitly compile a target in a non-default toolchain, you must give
+  Ninja the exact name of the output file relative to the build directory.
+```
+### <a name="nogncheck"></a>**nogncheck**: Skip an include line from checking.
+
+```
+  GN's header checker helps validate that the includes match the build
+  dependency graph. Sometimes an include might be conditional or otherwise
+  problematic, but you want to specifically allow it. In this case, it can be
+  whitelisted.
+
+  Include lines containing the substring "nogncheck" will be excluded from
+  header checking. The most common case is a conditional include:
+
+    #if defined(ENABLE_DOOM_MELON)
+    #include "tools/doom_melon/doom_melon.h"  // nogncheck
+    #endif
+
+  If the build file has a conditional dependency on the corresponding target
+  that matches the conditional include, everything will always link correctly:
+
+    source_set("mytarget") {
+      ...
+      if (enable_doom_melon) {
+        defines = [ "ENABLE_DOOM_MELON" ]
+        deps += [ "//tools/doom_melon" ]
+      }
+
+  But GN's header checker does not understand preprocessor directives, won't
+  know it matches the build dependencies, and will flag this include as
+  incorrect when the condition is false.
+```
+
+#### **More information**
+
+```
+  The topic "gn help check" has general information on how checking works and
+  advice on fixing problems. Targets can also opt-out of checking, see
+  "gn help check_includes".
+```
+### <a name="runtime_deps"></a>**Runtime dependencies**
+
+```
+  Runtime dependencies of a target are exposed via the "runtime_deps" category
+  of "gn desc" (see "gn help desc") or they can be written at build generation
+  time via write_runtime_deps(), or --runtime-deps-list-file (see "gn help
+  --runtime-deps-list-file").
+
+  To a first approximation, the runtime dependencies of a target are the set of
+  "data" files, data directories, and the shared libraries from all transitive
+  dependencies. Executables, shared libraries, and loadable modules are
+  considered runtime dependencies of themselves.
+```
+
+#### **Executables**
+
+```
+  Executable targets and those executable targets' transitive dependencies are
+  not considered unless that executable is listed in "data_deps". Otherwise, GN
+  assumes that the executable (and everything it requires) is a build-time
+  dependency only.
+```
+
+#### **Actions and copies**
+
+```
+  Action and copy targets that are listed as "data_deps" will have all of their
+  outputs and data files considered as runtime dependencies. Action and copy
+  targets that are "deps" or "public_deps" will have only their data files
+  considered as runtime dependencies. These targets can list an output file in
+  both the "outputs" and "data" lists to force an output file as a runtime
+  dependency in all cases.
+
+  The different rules for deps and data_deps are to express build-time (deps)
+  vs. run-time (data_deps) outputs. If GN counted all build-time copy steps as
+  data dependencies, there would be a lot of extra stuff, and if GN counted all
+  run-time dependencies as regular deps, the build's parallelism would be
+  unnecessarily constrained.
+
+  This rule can sometimes lead to unintuitive results. For example, given the
+  three targets:
+    A  --[data_deps]-->  B  --[deps]-->  ACTION
+  GN would say that A does not have runtime deps on the result of the ACTION,
+  which is often correct. But the purpose of the B target might be to collect
+  many actions into one logic unit, and the "data"-ness of A's dependency is
+  lost. Solutions:
+
+   - List the outputs of the action in its data section (if the results of
+     that action are always runtime files).
+   - Have B list the action in data_deps (if the outputs of the actions are
+     always runtime files).
+   - Have B list the action in both deps and data deps (if the outputs might be
+     used in both contexts and you don't care about unnecessary entries in the
+     list of files required at runtime).
+   - Split B into run-time and build-time versions with the appropriate "deps"
+     for each.
+```
+
+#### **Static libraries and source sets**
+
+```
+  The results of static_library or source_set targets are not considered
+  runtime dependencies since these are assumed to be intermediate targets only.
+  If you need to list a static library as a runtime dependency, you can
+  manually compute the .a/.lib file name for the current platform and list it
+  in the "data" list of a target (possibly on the static library target
+  itself).
+```
+
+#### **Multiple outputs**
+
+```
+  Linker tools can specify which of their outputs should be considered when
+  computing the runtime deps by setting runtime_outputs. If this is unset on
+  the tool, the default will be the first output only.
+```
+### <a name="source_expansion"></a>**How Source Expansion Works**
+
+```
+  Source expansion is used for the action_foreach and copy target types to map
+  source file names to output file names or arguments.
+
+  To perform source expansion in the outputs, GN maps every entry in the
+  sources to every entry in the outputs list, producing the cross product of
+  all combinations, expanding placeholders (see below).
+
+  Source expansion in the args works similarly, but performing the placeholder
+  substitution produces a different set of arguments for each invocation of the
+  script.
+
+  If no placeholders are found, the outputs or args list will be treated as a
+  static list of literal file names that do not depend on the sources.
+
+  See "gn help copy" and "gn help action_foreach" for more on how this is
+  applied.
+```
+
+#### **Placeholders**
+
+```
+  This section discusses only placeholders for actions. There are other
+  placeholders used in the definition of tools. See "gn help tool" for those.
+
+  {{source}}
+      The name of the source file including directory (*). This will generally
+      be used for specifying inputs to a script in the "args" variable.
+        "//foo/bar/baz.txt" => "../../foo/bar/baz.txt"
+
+  {{source_file_part}}
+      The file part of the source including the extension.
+        "//foo/bar/baz.txt" => "baz.txt"
+
+  {{source_name_part}}
+      The filename part of the source file with no directory or extension. This
+      will generally be used for specifying a transformation from a source file
+      to a destination file with the same name but different extension.
+        "//foo/bar/baz.txt" => "baz"
+
+  {{source_dir}}
+      The directory (*) containing the source file with no trailing slash.
+        "//foo/bar/baz.txt" => "../../foo/bar"
+
+  {{source_root_relative_dir}}
+      The path to the source file's directory relative to the source root, with
+      no leading "//" or trailing slashes. If the path is system-absolute,
+      (beginning in a single slash) this will just return the path with no
+      trailing slash. This value will always be the same, regardless of whether
+      it appears in the "outputs" or "args" section.
+        "//foo/bar/baz.txt" => "foo/bar"
+
+  {{source_gen_dir}}
+      The generated file directory (*) corresponding to the source file's path.
+      This will be different than the target's generated file directory if the
+      source file is in a different directory than the BUILD.gn file.
+        "//foo/bar/baz.txt" => "gen/foo/bar"
+
+  {{source_out_dir}}
+      The object file directory (*) corresponding to the source file's path,
+      relative to the build directory. this us be different than the target's
+      out directory if the source file is in a different directory than the
+      build.gn file.
+        "//foo/bar/baz.txt" => "obj/foo/bar"
+
+  {{source_target_relative}}
+      The path to the source file relative to the target's directory. This will
+      generally be used for replicating the source directory layout in the
+      output directory. This can only be used in actions and bundle_data
+      targets. It is an error to use in process_file_template where there is no
+      "target".
+        "//foo/bar/baz.txt" => "baz.txt"
+```
+
+#### **(*) Note on directories**
+
+```
+  Paths containing directories (except the source_root_relative_dir) will be
+  different depending on what context the expansion is evaluated in. Generally
+  it should "just work" but it means you can't concatenate strings containing
+  these values with reasonable results.
+
+  Details: source expansions can be used in the "outputs" variable, the "args"
+  variable, and in calls to "process_file_template". The "args" are passed to a
+  script which is run from the build directory, so these directories will
+  relative to the build directory for the script to find. In the other cases,
+  the directories will be source- absolute (begin with a "//") because the
+  results of those expansions will be handled by GN internally.
+```
+
+#### **Examples**
+
+```
+  Non-varying outputs:
+    action("hardcoded_outputs") {
+      sources = [ "input1.idl", "input2.idl" ]
+      outputs = [ "$target_out_dir/output1.dat",
+                  "$target_out_dir/output2.dat" ]
+    }
+  The outputs in this case will be the two literal files given.
+
+  Varying outputs:
+    action_foreach("varying_outputs") {
+      sources = [ "input1.idl", "input2.idl" ]
+      outputs = [ "{{source_gen_dir}}/{{source_name_part}}.h",
+                  "{{source_gen_dir}}/{{source_name_part}}.cc" ]
+    }
+  Performing source expansion will result in the following output names:
+    //out/Debug/obj/mydirectory/input1.h
+    //out/Debug/obj/mydirectory/input1.cc
+    //out/Debug/obj/mydirectory/input2.h
+    //out/Debug/obj/mydirectory/input2.cc
+```
+### <a name="switch_list"></a>**Available global switches**
+
+```
+  Do "gn help --the_switch_you_want_help_on" for more. Individual commands may
+  take command-specific switches not listed here. See the help on your specific
+  command for more.
+```
+```
+    *   --args: Specifies build arguments overrides.
+    *   --color: Force colored output.
+    *   --dotfile: Override the name of the ".gn" file.
+    *   --fail-on-unused-args: Treat unused build args as fatal errors.
+    *   --markdown: Write help output in the Markdown format.
+    *   --ninja-executable: Set the Ninja executable.
+    *   --nocolor: Force non-colored output.
+    *   -q: Quiet mode. Don't print output on success.
+    *   --root: Explicitly specify source root.
+    *   --root-target: Override the root target.
+    *   --runtime-deps-list-file: Save runtime dependencies for targets in file.
+    *   --script-executable: Set the executable used to execute scripts.
+    *   --threads: Specify number of worker threads.
+    *   --time: Outputs a summary of how long everything took.
+    *   --tracelog: Writes a Chrome-compatible trace log to the given file.
+    *   -v: Verbose logging.
+    *   --version: Prints the GN version number and exits.
+```
+
diff --git a/docs/standalone.md b/docs/standalone.md
new file mode 100644 (file)
index 0000000..08d589b
--- /dev/null
@@ -0,0 +1,44 @@
+# Introduction
+
+This page is about how to design a project that can build independently
+with GN but also be brought into the Chrome build.
+
+GN is in principle no different than GYP in that there is some core
+configuration that must be the same between both the standalone build
+and the Chrome build. However, GN is much more explicit in its naming
+and configuration, so the similarities between the two builds are also
+much more explicit and there is less flexibility in how things are
+configured.
+
+# What you need for a minimal GN build
+
+Requirements:
+
+  * A master build config file. Chrome's is `//build/config/BUILDCONFIG.gn`
+  * A separate build file for the toolchain definition. It's not a good idea
+    to put these in a BUILD.gn file shared with any target definitions for
+    complex reasons. Chrome's are in `//build/toolchain/<platform>/BUILD.gn`.
+  * A `BUILD.gn` file in the root directory. This will be loaded after the
+    build config file to start the build.
+
+You may want a `.gn` file in the root directory. When you run GN it
+recursively looks up the directory tree until it finds this file, and it
+treats the containing directory as the "source root". This file also
+defines the location of the master build config file:
+
+  * See Chrome's `src/.gn` file.
+  * Unlike Chrome, you probably don't need to define a secondary root.
+  * see `gn help dotfile` for more.
+
+Adding a `.gn` file in a repository that is pulled into Chrome means
+that then running GN in your subdirectory will configure a build for
+your subproject rather than for all of Chrome. This could be an
+advantage or a disadvantage.
+
+If you are in a directory with such a file and you want to not use it
+(e.g., to do the full Chrome build instead), you can use the command-line
+flags `--root` and `--dotfile` to set the values you want.
+
+If you want a completely standalone build that has nothing to do with Chrome
+and doesn't use Chrome's `//build` files, you can look at an example in
+[//tools/gn/example](../tools/gn/example).
diff --git a/docs/style_guide.md b/docs/style_guide.md
new file mode 100644 (file)
index 0000000..8c27a8c
--- /dev/null
@@ -0,0 +1,306 @@
+# GN Style Guide
+
+[TOC]
+
+## Naming and ordering within the file
+
+### Location of build files
+
+It usually makes sense to have more build files closer to the code than
+fewer ones at the top level; this is in contrast with what we did with
+GYP. This makes things easier to find, and also makes the set of owners
+required for reviews smaller since changes are more focused to particular
+subdirectories.
+
+### Targets
+
+  * Most BUILD files should have a target with the same name as the
+    directory. This target should be the first target.
+  * Other targets should be in some logical order -- usually
+    more important targets will be first, and unit tests will follow the
+    corresponding target. If there's no clear ordering, consider
+    alphabetical order.
+  * Test support libraries should be static libraries named "test\_support".
+    For example, "//ui/compositor:test\_support". Test support libraries should
+    include as public deps the non-test-support version of the library
+    so tests need only depend on the test\_support target (rather than
+    both).
+
+Naming advice
+
+  * Targets and configs should be named using lowercase with underscores
+    separating words, unless there is a strong reason to do otherwise.
+  * Source sets, groups, and static libraries do not need globally unique names.
+    Prefer to give such targets short, non-redundant names without worrying
+    about global uniqueness. For example, it looks much better to write a
+    dependency as `"//mojo/public/bindings"` rather than
+    `"//mojo/public/bindings:mojo_bindings"`
+  * Shared libraries (and by extension, components) must have globally unique
+    output names. Give such targets short non-unique names above, and then
+    provide a globally unique `output_name` for that target.
+  * Executables and tests should be given a globally unique name. Technically
+    only the output names must be unique, but since only the output names
+    appear in the shell and on bots, it's much less confusing if the name
+    matches the other places the executable appears.
+
+### Configs
+
+  * A config associated with a single target should be named the same as
+    the target with `_config` following it.
+  * A config should appear immediately before the corresponding target
+    that uses it.
+
+### Example
+
+Example for the `src/foo/BUILD.gn` file:
+
+```
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Config for foo is named foo_config and immediately precedes it in the file.
+config("foo_config") {
+}
+
+# Target matching path name is the first target.
+executable("foo") {
+}
+
+# Test for foo follows it.
+test("foo_unittests") {
+}
+
+config("bar_config") {
+}
+
+source_set("bar") {
+}
+```
+
+## Ordering within a target
+
+  1. `output_name` / `visibility` / `testonly`
+  2. `sources`
+  3. `cflags`, `include_dirs`, `defines`, `configs` etc. in whatever
+     order makes sense to you.
+  4. `public_deps`
+  5. `deps`
+
+### Conditions
+
+Simple conditions affecting just one variable (e.g. adding a single
+source or adding a flag for one particular OS) can go beneath the
+variable they affect. More complicated conditions affecting more than
+one thing should go at the bottom.
+
+Conditions should be written to minimize the number of conditional blocks.
+
+## Formatting and indenting
+
+GN contains a built-in code formatter which defines the formatting style.
+Some additional notes:
+
+  * Variables are `lower_case_with_underscores`.
+  * Comments should be complete sentences with periods at the end.
+  * Compiler flags and such should always be commented with what they do
+    and why the flag is needed.
+
+### Sources
+
+Prefer to list sources only once. It is OK to conditionally include sources
+rather than listing them all at the top and then conditionally excluding them
+when they don't apply. Conditional inclusion is often clearer since a file is
+only listed once and it's easier to reason about when reading.
+
+```
+  sources = [
+    "main.cc",
+  ]
+  if (use_aura) {
+    sources += [ "thing_aura.cc" ]
+  }
+  if (use_gtk) {
+    sources += [ "thing_gtk.cc" ]
+  }
+```
+
+### Deps
+
+  * Deps should be in alphabetical order.
+  * Deps within the current file should be written first and not
+    qualified with the file name (just `:foo`).
+  * Other deps should always use fully-qualified path names unless
+    relative ones are required for some reason.
+
+```
+  deps = [
+    ":a_thing",
+    ":mystatic",
+    "//foo/bar:other_thing",
+    "//foo/baz:that_thing",
+  ]
+```
+
+### Import
+
+Use fully-qualified paths for imports:
+
+```
+import("//foo/bar/baz.gni")  # Even if this file is in the foo/bar directory
+```
+
+## Usage
+
+### Source sets versus static libraries
+
+Source sets and static libraries can be used interchangeably in most cases. If
+you're unsure what to use, a source set is almost never wrong and is less likely
+to cause problems, but on a large project using the right kind of target can
+be important, so you should know about the following tradeoffs.
+
+Static libraries follow different linking rules. When a static library is
+included in a link, only the object files that contain unresolved symbols will
+be brought into the build. Source sets result in every object file being added
+to the link line of the final binary.
+
+  * If you're eventually linking code into a component, shared library, or
+    loadable module, you normally need to use source sets. This is because
+    object files with no symbols referenced from within the shared library will
+    not be linked into the final library at all. This omission will happen even
+    if that object file has a symbol marked for export that targets dependent
+    on that shared library need. This will result in undefined symbols when
+    linking later targets.
+
+  * Unit tests (and anything else with static initializers with side effects)
+    must use source sets. The gtest TEST macros create static initializers
+    that register the test. But since no code references symbols in the object
+    file, linking a test into a static library and then into a test executable
+    means the tests will get stripped.
+
+  * On some platforms, static libraries may involve duplicating all of the
+    data in the object files that comprise it. This takes more disk space and
+    for certain very large libraries in configurations with very large object
+    files can cause internal limits on the size of static libraries to be
+    exceeded. Source sets do not have this limitation. Some targets switch
+    between source sets and static libraries depending on the build
+    configuration to avoid this problem. Some platforms (or toolchains) may
+    support something called "thin archives" which don't have this problem;
+    but you can't rely on this as a portable solution.
+
+  * Source sets can have no sources, while static libraries will give strange
+    platform-specific errors if they have no sources. If a target has only
+    headers (for include checking purposes) or conditionally has no sources on
+    some platforms, use a source set.
+
+  * In cases where a lot of the symbols are not needed for a particular link
+    (this especially happens when linking test binaries), putting that code in
+    a static library can dramatically increase linking performance. This is
+    because the object files not needed for the link are never considered in
+    the first place, rather than forcing the linker to strip the unused code
+    in a later pass when nothing references it.
+
+### Components versus shared libraries versus source sets
+
+A component is a Chrome template (rather than a built-in GN concept) that
+expands either to a shared library or a static library / source set depending
+on the value of the `is_component_build` variable. This allows release builds
+to be linked statically in a large binary, but for developers to use shared
+libraries for most operations. Chrome developers should almost always use
+a component instead of shared library directly.
+
+Much like the source set versus static library tradeoff, there's no hard
+and fast rule as to when you should use a component or not. Using
+components can significantly speed up incremental builds by making
+linking much faster, but they require you to have to think about which
+symbols need to be exported from the target.
+
+### Loadable modules versus shared libraries
+
+A shared library will be listed on the link line of dependent targets and will
+be loaded automatically by the operating system when the application starts
+and symbols automatically resolved. A loadable module will not be linked
+directly and the application must manually load it.
+
+On Windows and Linux shared libraries and loadable modules result in the same
+type of file (`.dll` and `.so`, respectively). The only difference is in how
+they are linked to dependent targets. On these platforms, having a `deps`
+dependency on a loadable module is the same as having a `data_deps`
+(non-linked) dependency on a shared library.
+
+On Mac, these targets have different formats: a shared library will generate a
+`.dylib` file and a loadable module will generate a `.so` file.
+
+Use loadable modules for things like plugins. In the case of plugin-like
+libraries, it's good practice to use both a loadable module for the target type
+(even for platforms where it doesn't matter) and data deps for targets that
+depend on it so it's clear from both places that how the library will be linked
+and loaded.
+
+## Build arguments
+
+### Scope
+
+Build arguments should be scoped to a unit of behavior, e.g. enabling a feature.
+Typically an argument would be declared in an imported file to share it with
+the subset of the build that could make use of it.
+
+Chrome has many legacy flags in `//build/config/features.gni`,
+`//build/config/ui.gni`. These locations are deprecated. Feature flags should
+go along with the code for the feature. Many browser-level features can go
+somewhere in `//chrome/` without lower-level code knowing about it. Some
+UI environment flags can go into `//ui/`, and many flags can also go with
+the corresponding code in `//components/`. You can write a `.gni` file in
+components and have build files in chrome or content import it if necessary.
+
+The way to think about things in the `//build` directory is that this is
+DEPSed into various projects like V8 and WebRTC. Build flags specific to
+code outside of the build directory shouldn't be in the build directory, and
+V8 shouldn't get feature defines for Chrome features.
+
+New feature defines should use the buildflag system. See
+`//build/buildflag_header.gni` which allows preprocessor defines to be
+modularized without many of the disadvantages that made us use global defines
+in the past.
+
+### Type
+
+Arguments support all the [GN language types](language.md#Language).
+
+In the vast majority of cases `boolean` is the preferred type, since most
+arguments are enabling or disabling features or includes.
+
+`String`s are typically used for filepaths. They are also used for enumerated
+types, though `integer`s are sometimes used as well.
+
+### Naming conventions
+
+While there are no hard and fast rules around argument naming there are
+many common conventions. If you ever want to see the current list of argument
+names and default values for your current checkout use
+`gn args out/Debug --list --short`.
+
+`use_foo` - indicates dependencies or major codepaths to include (e.g.
+`use_open_ssl`, `use_ozone`, `use_cups`)
+
+`enable_foo` - indicates feature or tools to be enabled (e.g.
+`enable_google_now`, `enable_nacl`, `enable_remoting`, `enable_pdf`)
+
+`disable_foo` - _NOT_ recommended, use `enable_foo` instead with swapped default
+value
+
+`is_foo` - usually a global state descriptor (e.g. `is_chrome_branded`,
+`is_desktop_linux`); poor choice for non-globals
+
+`foo_use_bar` - prefixes can be used to indicate a limited scope for an argument
+(e.g. `rtc_use_h264`, `v8_use_snapshot`)
+
+#### Variables
+
+Prefix top-level local variables within `.gni` files with an underscore. This
+prefix causes variables to be unavailable to importing scripts.
+
+```
+_this_var_will_not_be_exported = 1
+but_this_one_will = 2
+```
diff --git a/examples/ios/.gitignore b/examples/ios/.gitignore
new file mode 100644 (file)
index 0000000..6a3417b
--- /dev/null
@@ -0,0 +1 @@
+/out/
diff --git a/examples/ios/.gn b/examples/ios/.gn
new file mode 100644 (file)
index 0000000..e5b6d4a
--- /dev/null
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/BUILDCONFIG.gn"
diff --git a/examples/ios/BUILD.gn b/examples/ios/BUILD.gn
new file mode 100644 (file)
index 0000000..bd71b4f
--- /dev/null
@@ -0,0 +1,10 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("all") {
+  deps = [
+    "//app:hello",
+    "//host:username",
+  ]
+}
diff --git a/examples/ios/app/AppDelegate.h b/examples/ios/app/AppDelegate.h
new file mode 100644 (file)
index 0000000..54d31eb
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@end
diff --git a/examples/ios/app/AppDelegate.m b/examples/ios/app/AppDelegate.m
new file mode 100644 (file)
index 0000000..b6f94fb
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "app/AppDelegate.h"
+
+#import "app/Foo.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  NSLog(@"%@", [[[FooWrapper alloc] init] helloWithName:@"World"]);
+  return YES;
+}
+
+#pragma mark - UISceneSession lifecycle
+
+- (UISceneConfiguration*)application:(UIApplication*)application
+    configurationForConnectingSceneSession:
+        (UISceneSession*)connectingSceneSession
+                                   options:(UISceneConnectionOptions*)options {
+  return
+      [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
+                                     sessionRole:connectingSceneSession.role];
+}
+
+- (void)application:(UIApplication*)application
+    didDiscardSceneSessions:(NSSet<UISceneSession*>*)sceneSessions {
+}
+
+@end
diff --git a/examples/ios/app/BUILD.gn b/examples/ios/app/BUILD.gn
new file mode 100644 (file)
index 0000000..fdb3d03
--- /dev/null
@@ -0,0 +1,67 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/templates/ios_app_bundle.gni")
+import("//build/config/ios/templates/storyboards.gni")
+
+ios_app_bundle("hello") {
+  output_name = "Hello"
+
+  info_plist = "resources/Info.plist"
+
+  sources = [
+    "AppDelegate.h",
+    "AppDelegate.m",
+    "SceneDelegate.h",
+    "SceneDelegate.m",
+    "ViewController.h",
+    "ViewController.m",
+    "main.m",
+  ]
+
+  frameworks = [
+    "CoreGraphics.framework",
+    "Foundation.framework",
+    "UIKit.framework",
+  ]
+
+  deps = [
+    ":foo",
+    ":storyboards",
+    "//shared:hello_framework",
+    "//shared:hello_framework+bundle",
+  ]
+}
+
+storyboards("storyboards") {
+  sources = [
+    "resources/LaunchScreen.storyboard",
+    "resources/Main.storyboard",
+  ]
+}
+
+source_set("baz") {
+  module_name = "Baz"
+  sources = [ "Baz.swift" ]
+}
+
+source_set("bar") {
+  module_name = "Bar"
+  sources = [ "Bar.swift" ]
+  deps = [ ":baz" ]
+}
+
+group("bar_indirect") {
+  public_deps = [ ":bar" ]
+}
+
+source_set("foo") {
+  module_name = "Foo"
+  bridge_header = "Foo-Bridging-Header.h"
+  sources = [
+    "Foo.swift",
+    "FooWrapper.swift",
+  ]
+  deps = [ ":bar_indirect" ]
+}
diff --git a/examples/ios/app/Bar.swift b/examples/ios/app/Bar.swift
new file mode 100644 (file)
index 0000000..9e35c9d
--- /dev/null
@@ -0,0 +1,8 @@
+
+import Baz;
+
+public class Greeter {
+  public static func greet(greeting: String, name: String, from: String) -> String {
+    return greeting + ", " + name + " (from " + from + ")";
+  }
+}
diff --git a/examples/ios/app/Baz.swift b/examples/ios/app/Baz.swift
new file mode 100644 (file)
index 0000000..f389578
--- /dev/null
@@ -0,0 +1,2 @@
+
+class Baz {}
diff --git a/examples/ios/app/Foo-Bridging-Header.h b/examples/ios/app/Foo-Bridging-Header.h
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/ios/app/Foo.swift b/examples/ios/app/Foo.swift
new file mode 100644 (file)
index 0000000..5e9faa1
--- /dev/null
@@ -0,0 +1,12 @@
+
+import Bar
+
+class Foo {
+  var name: String;
+  public init(name: String) {
+    self.name = name;
+  }
+  public func hello(name: String) -> String {
+    return Greeter.greet(greeting: "Hello", name: name, from: self.name);
+  }
+}
diff --git a/examples/ios/app/FooWrapper.swift b/examples/ios/app/FooWrapper.swift
new file mode 100644 (file)
index 0000000..a82c8a8
--- /dev/null
@@ -0,0 +1,10 @@
+
+import Foundation;
+
+@objc
+public class FooWrapper : NSObject {
+  @objc
+  public func hello(name: String) -> String {
+    return Foo(name: "Foo").hello(name: name);
+  }
+}
diff --git a/examples/ios/app/SceneDelegate.h b/examples/ios/app/SceneDelegate.h
new file mode 100644 (file)
index 0000000..ef1888f
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UIKit/UIKit.h>
+
+@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
+
+@property(strong, nonatomic) UIWindow* window;
+
+@end
diff --git a/examples/ios/app/SceneDelegate.m b/examples/ios/app/SceneDelegate.m
new file mode 100644 (file)
index 0000000..5e20b9e
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "app/SceneDelegate.h"
+
+@implementation SceneDelegate
+
+- (void)scene:(UIScene*)scene
+    willConnectToSession:(UISceneSession*)session
+                 options:(UISceneConnectionOptions*)connectionOptions {
+}
+
+- (void)sceneDidDisconnect:(UIScene*)scene {
+}
+
+- (void)sceneDidBecomeActive:(UIScene*)scene {
+}
+
+- (void)sceneWillResignActive:(UIScene*)scene {
+}
+
+- (void)sceneWillEnterForeground:(UIScene*)scene {
+}
+
+- (void)sceneDidEnterBackground:(UIScene*)scene {
+}
+
+@end
diff --git a/examples/ios/app/ViewController.h b/examples/ios/app/ViewController.h
new file mode 100644 (file)
index 0000000..4076e40
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/examples/ios/app/ViewController.m b/examples/ios/app/ViewController.m
new file mode 100644 (file)
index 0000000..e0a80cb
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <HelloShared/HelloShared.h>
+
+#import "app/ViewController.h"
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  UILabel* label = [self labelWithText:[Greetings greet]];
+  [self addCenteredView:label toParentView:self.view];
+}
+
+- (UILabel*)labelWithText:(NSString*)text {
+  UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero];
+  label.text = text;
+  [label sizeToFit];
+  return label;
+}
+
+- (void)addCenteredView:(UIView*)view toParentView:(UIView*)parentView {
+  view.center = [parentView convertPoint:parentView.center
+                                fromView:parentView.superview];
+  [parentView addSubview:view];
+}
+
+@end
diff --git a/examples/ios/app/main.m b/examples/ios/app/main.m
new file mode 100644 (file)
index 0000000..b01cb7a
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UIKit/UIKit.h>
+#import "app/AppDelegate.h"
+
+int main(int argc, char** argv) {
+  NSString* appDelegateClassName;
+  @autoreleasepool {
+    appDelegateClassName = NSStringFromClass([AppDelegate class]);
+  }
+
+  return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+}
diff --git a/examples/ios/app/resources/Info.plist b/examples/ios/app/resources/Info.plist
new file mode 100644 (file)
index 0000000..7b6037c
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>$(DEVELOPMENT_LANGUAGE)</string>
+       <key>CFBundleExecutable</key>
+       <string>$(EXECUTABLE_NAME)</string>
+       <key>CFBundleIdentifier</key>
+       <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>$(PRODUCT_NAME)</string>
+       <key>CFBundlePackageType</key>
+       <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleVersion</key>
+       <string>1</string>
+       <key>LSRequiresIPhoneOS</key>
+       <true/>
+       <key>UIApplicationSceneManifest</key>
+       <dict>
+               <key>UIApplicationSupportsMultipleScenes</key>
+               <false/>
+               <key>UISceneConfigurations</key>
+               <dict>
+                       <key>UIWindowSceneSessionRoleApplication</key>
+                       <array>
+                               <dict>
+                                       <key>UISceneConfigurationName</key>
+                                       <string>Default Configuration</string>
+                                       <key>UISceneDelegateClassName</key>
+                                       <string>SceneDelegate</string>
+                                       <key>UISceneStoryboardFile</key>
+                                       <string>Main</string>
+                               </dict>
+                       </array>
+               </dict>
+       </dict>
+       <key>UILaunchStoryboardName</key>
+       <string>LaunchScreen</string>
+       <key>UIMainStoryboardFile</key>
+       <string>Main</string>
+       <key>UIRequiredDeviceCapabilities</key>
+       <array>
+               <string>armv7</string>
+       </array>
+       <key>UISupportedInterfaceOrientations</key>
+       <array>
+               <string>UIInterfaceOrientationPortrait</string>
+               <string>UIInterfaceOrientationLandscapeLeft</string>
+               <string>UIInterfaceOrientationLandscapeRight</string>
+       </array>
+       <key>UISupportedInterfaceOrientations~ipad</key>
+       <array>
+               <string>UIInterfaceOrientationPortrait</string>
+               <string>UIInterfaceOrientationPortraitUpsideDown</string>
+               <string>UIInterfaceOrientationLandscapeLeft</string>
+               <string>UIInterfaceOrientationLandscapeRight</string>
+       </array>
+</dict>
+</plist>
diff --git a/examples/ios/app/resources/LaunchScreen.storyboard b/examples/ios/app/resources/LaunchScreen.storyboard
new file mode 100644 (file)
index 0000000..865e932
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/examples/ios/app/resources/Main.storyboard b/examples/ios/app/resources/Main.storyboard
new file mode 100644 (file)
index 0000000..808a21c
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/examples/ios/build/BUILD.gn b/examples/ios/build/BUILD.gn
new file mode 100644 (file)
index 0000000..2831117
--- /dev/null
@@ -0,0 +1,88 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/deployment_target.gni")
+
+config("compiler") {
+  configs = [
+    ":include_dirs",
+    ":cpp_standard",
+    ":objc_use_arc",
+    ":objc_abi_version",
+  ]
+  cflags = [ "-g" ]
+  swiftflags = [ "-g" ]
+}
+
+config("shared_binary") {
+  if (current_os == "ios" || current_os == "mac") {
+    configs = [
+      ":rpath_config",
+      ":swift_libdir",
+    ]
+  }
+}
+
+config("objc_abi_version") {
+  cflags_objc = [ "-fobjc-abi-version=2" ]
+  cflags_objcc = cflags_objc
+  ldflags = [
+    "-Xlinker",
+    "-objc_abi_version",
+    "-Xlinker",
+    "2",
+  ]
+}
+
+config("include_dirs") {
+  include_dirs = [
+    "//",
+    root_gen_dir,
+  ]
+}
+
+config("objc_use_arc") {
+  cflags_objc = [
+    "-fobjc-arc",
+    "-fobjc-weak",
+  ]
+  cflags_objcc = cflags_objc
+}
+
+config("cpp_standard") {
+  cflags_c = [ "--std=c11" ]
+  cflags_cc = [
+    "--std=c++17",
+    "--stdlib=libc++",
+  ]
+  ldflags = [ "--stdlib=libc++" ]
+}
+
+if (current_os == "ios" || current_os == "mac") {
+  config("rpath_config") {
+    ldflags = [
+      "-Xlinker",
+      "-rpath",
+      "-Xlinker",
+      "@executable_path/Frameworks",
+      "-Xlinker",
+      "-rpath",
+      "-Xlinker",
+      "@loader_path/Frameworks",
+    ]
+  }
+
+  _sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+                          [
+                            "--target-cpu",
+                            current_cpu,
+                            "--deployment-target",
+                            ios_deployment_target,
+                          ],
+                          "json")
+
+  config("swift_libdir") {
+    lib_dirs = [ "${_sdk_info.sdk_path}/usr/lib/swift" ]
+  }
+}
diff --git a/examples/ios/build/BUILDCONFIG.gn b/examples/ios/build/BUILDCONFIG.gn
new file mode 100644 (file)
index 0000000..53ee3d9
--- /dev/null
@@ -0,0 +1,43 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (target_os == "") {
+  target_os = "ios"
+}
+if (target_cpu == "") {
+  target_cpu = host_cpu
+}
+if (current_cpu == "") {
+  current_cpu = target_cpu
+}
+if (current_os == "") {
+  current_os = target_os
+}
+
+# All binary targets will get this list of configs by default.
+_shared_binary_target_configs = [ "//build:compiler" ]
+
+# Apply that default list to the binary target types.
+set_defaults("executable") {
+  configs = _shared_binary_target_configs
+  configs += [ "//build:shared_binary" ]
+}
+set_defaults("static_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("shared_library") {
+  configs = _shared_binary_target_configs
+  configs += [ "//build:shared_binary" ]
+}
+set_defaults("source_set") {
+  configs = _shared_binary_target_configs
+}
+
+set_default_toolchain("//build/toolchain/$target_os:clang_$target_cpu")
+
+if (target_os == "ios") {
+  host_toolchain = "//build/toolchain/$host_os:clang_$host_cpu"
+} else {
+  host_toolchain = default_toolchain
+}
diff --git a/examples/ios/build/config/ios/BUILD.gn b/examples/ios/build/config/ios/BUILD.gn
new file mode 100644 (file)
index 0000000..47902c9
--- /dev/null
@@ -0,0 +1,24 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/sdk_info.gni")
+import("//build/config/ios/templates/merge_plist.gni")
+
+merge_plist("compiler_plist") {
+  substitutions = {
+    COMPILER_NAME = sdk_info.compiler
+    MACOS_BUILD = sdk_info.macos_build
+    PLATFORM_BUILD = sdk_info.sdk_build
+    PLATFORM_DISPLAY_NAME = sdk_info.platform_name
+    PLATFORM_NAME = sdk_info.platform
+    PLATFORM_VERSION = sdk_info.sdk_version
+    SDK_BUILD = sdk_info.sdk_build
+    SDK_NAME = sdk_info.sdk
+    XCODE_BUILD = sdk_info.xcode_build
+    XCODE_VERSION = sdk_info.xcode_version
+  }
+
+  output = "$target_out_dir/compiler_plist/Info.plist"
+  plists = [ "//build/config/ios/resources/compiler-Info.plist" ]
+}
diff --git a/examples/ios/build/config/ios/bundle_identifier_prefix.gni b/examples/ios/build/config/ios/bundle_identifier_prefix.gni
new file mode 100644 (file)
index 0000000..97c47cc
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  # Default bundle identifier prefix.
+  default_bundle_identifier_prefix = "com.google"
+}
diff --git a/examples/ios/build/config/ios/deployment_target.gni b/examples/ios/build/config/ios/deployment_target.gni
new file mode 100644 (file)
index 0000000..7180488
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  # Maximum deployment target. Automatically detected by sdk_info.py but
+  # needs to be specified to a version < 11.0 if targetting 32-bit archs.
+  ios_deployment_target = "13.0"
+}
diff --git a/examples/ios/build/config/ios/resources/Entitlements-Simulated.plist b/examples/ios/build/config/ios/resources/Entitlements-Simulated.plist
new file mode 100644 (file)
index 0000000..0a4badf
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>application-identifier</key>
+  <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+  <key>keychain-access-groups</key>
+  <array>
+    <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+  </array>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/resources/Info.plist b/examples/ios/build/config/ios/resources/Info.plist
new file mode 100644 (file)
index 0000000..9bcb244
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>$(DEVELOPMENT_LANGUAGE)</string>
+       <key>CFBundleExecutable</key>
+       <string>$(EXECUTABLE_NAME)</string>
+       <key>CFBundleIdentifier</key>
+       <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>$(PRODUCT_NAME)</string>
+       <key>CFBundlePackageType</key>
+       <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleVersion</key>
+       <string>$(CURRENT_PROJECT_VERSION)</string>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/resources/compiler-Info.plist b/examples/ios/build/config/ios/resources/compiler-Info.plist
new file mode 100644 (file)
index 0000000..0a02517
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>DTSDKName</key>
+       <string>$(SDK_NAME)</string>
+       <key>DTXcode</key>
+       <string>$(XCODE_VERSION)</string>
+       <key>DTSDKBuild</key>
+       <string>$(SDK_BUILD)</string>
+       <key>BuildMachineOSBuild</key>
+       <string>$(MACOS_BUILD)</string>
+       <key>DTPlatformName</key>
+       <string>$(PLATFORM_NAME)</string>
+       <key>DTCompiler</key>
+       <string>$(COMPILER_NAME)</string>
+       <key>DTPlatformVersion</key>
+       <string>$(PLATFORM_VERSION)</string>
+       <key>DTXcodeBuild</key>
+       <string>$(XCODE_BUILD)</string>
+       <key>DTPlatformBuild</key>
+       <string>$(PLATFORM_BUILD)</string>
+       <key>CFBundleSupportedPlatforms</key>
+       <array>
+               <string>$(PLATFORM_DISPLAY_NAME)</string>
+       </array>
+       <key>UIDeviceFamily</key>
+       <array>
+               <string>1</string>
+               <string>2</string>
+       </array>
+</dict>
+</plist>
diff --git a/examples/ios/build/config/ios/scripts/compile_storyboard.py b/examples/ios/build/config/ios/scripts/compile_storyboard.py
new file mode 100644 (file)
index 0000000..2fa1d20
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Compiles a .storyboard file.
+"""
+
+import argparse
+import os
+import subprocess
+import sys
+
+
+def CompileStoryboard(storyboard, out, ios_deployment_target):
+  """Compiles |storyboard| storyboard to |out| for |ios_deployment_target|."""
+  subprocess.check_call([
+      'ibtool', '--target-device', 'iphone', '--target-device', 'ipad',
+      '--auto-activate-custom-fonts', '--minimum-deployment-target',
+      ios_deployment_target, '--compilation-directory', out,
+      storyboard,
+  ])
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      'input',
+      help='path to the .storyboard file to compile')
+  parser.add_argument(
+      '-o', '--output', required=True,
+      help='path to the result')
+  parser.add_argument(
+      '-t', '--minimum-deployment-target', required=True,
+      help='iOS deployment target')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  CompileStoryboard(
+      os.path.abspath(args.input),
+      os.path.dirname(os.path.abspath(args.output)),
+      args.minimum_deployment_target)
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py b/examples/ios/build/config/ios/scripts/find_app_identifier_prefix.py
new file mode 100644 (file)
index 0000000..cf38b10
--- /dev/null
@@ -0,0 +1,119 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Finds the team identifier to use for code signing bundle given its
+bundle identifier.
+"""
+
+import argparse
+import fnmatch
+import glob
+import json
+import os
+import plistlib
+import subprocess
+import sys
+
+
+class ProvisioningProfile(object):
+
+  def __init__(self, mobileprovision_path):
+    self._path = mobileprovision_path
+    self._data = plistlib.readPlistFromString(
+        subprocess.check_output(
+            ['security', 'cms', '-D', '-i', mobileprovision_path]))
+
+  @property
+  def application_identifier_pattern(self):
+    return self._data.get('Entitlements', {}).get('application-identifier', '')
+
+  @property
+  def app_identifier_prefix(self):
+    return self._data.get('ApplicationIdentifierPrefix', [''])[0]
+
+  def ValidToSignBundle(self, bundle_identifier):
+    """Returns whether the provisioning profile can sign |bundle_identifier|."""
+    return fnmatch.fnmatch(
+        self.app_identifier_prefix + '.' + bundle_identifier,
+        self.application_identifier_pattern)
+
+
+def GetProvisioningProfilesDir():
+  """Returns the location of the locally installed provisioning profiles."""
+  return os.path.join(
+      os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles')
+
+
+def ListProvisioningProfiles():
+  """Returns a list of all installed provisioning profiles."""
+  return glob.glob(
+      os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision'))
+
+
+def LoadProvisioningProfile(mobileprovision_path):
+  """Loads the Apple Property List embedded in |mobileprovision_path|."""
+  return ProvisioningProfile(mobileprovision_path)
+
+
+def ListValidProvisioningProfiles(bundle_identifier):
+  """Returns a list of provisioning profile valid for |bundle_identifier|."""
+  result = []
+  for mobileprovision_path in ListProvisioningProfiles():
+    mobileprovision = LoadProvisioningProfile(mobileprovision_path)
+    if mobileprovision.ValidToSignBundle(bundle_identifier):
+      result.append(mobileprovision)
+  return result
+
+
+def FindProvisioningProfile(bundle_identifier):
+  """Returns the path to the provisioning profile for |bundle_identifier|."""
+  return max(
+      ListValidProvisioningProfiles(bundle_identifier),
+      key=lambda p: len(p.application_identifier_pattern))
+
+
+def GenerateSubsitutions(bundle_identifier, mobileprovision):
+  if mobileprovision:
+    app_identifier_prefix = mobileprovision.app_identifier_prefix + '.'
+  else:
+    app_identifier_prefix = '*.'
+
+  return {
+      'CFBundleIdentifier': bundle_identifier,
+      'AppIdentifierPrefix': app_identifier_prefix
+  }
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-b', '--bundle-identifier', required=True,
+      help='bundle identifier for the application')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path to the result; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  mobileprovision = FindProvisioningProfile(args.bundle_identifier)
+  substitutions = GenerateSubsitutions(args.bundle_identifier, mobileprovision)
+
+  if args.output == '-':
+    sys.stdout.write(json.dumps(substitutions))
+  else:
+    with open(args.output, 'w') as output:
+      output.write(json.dumps(substitutions))
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/generate_umbrella_header.py b/examples/ios/build/config/ios/scripts/generate_umbrella_header.py
new file mode 100644 (file)
index 0000000..1137376
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Generates an umbrella header file that #import all public header of a
+binary framework.
+"""
+
+import argparse
+import os
+import sys
+
+
+def GenerateImport(header):
+  """Returns a string for importing |header|."""
+  return '#import "%s"\n' % os.path.basename(header)
+
+
+def GenerateUmbrellaHeader(headers):
+  """Returns a string with the content of the umbrella header."""
+  return ''.join([ GenerateImport(header) for header in headers ])
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      'headers', nargs='+',
+      help='path to the public heeaders')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path of the output file to create; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  content = GenerateUmbrellaHeader(args.headers)
+
+  if args.output == '-':
+    sys.stdout.write(content)
+  else:
+    with open(args.output, 'w') as output:
+      output.write(content)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/merge_plist.py b/examples/ios/build/config/ios/scripts/merge_plist.py
new file mode 100644 (file)
index 0000000..452cf53
--- /dev/null
@@ -0,0 +1,134 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Merges multiple Apple Property List files (.plist) and perform variables
+substitutions $(VARIABLE) in the Property List string values.
+"""
+
+import argparse
+import json
+import re
+import subprocess
+import sys
+
+
+# Pattern representing a variable to substitue in a string value.
+VARIABLE_PATTERN = re.compile(r'\$\(([^)]*)\)')
+
+
+def LoadPlist(plist_path):
+  """Loads Apple Property List file at |plist_path|."""
+  return json.loads(
+      subprocess.check_output(
+          ['plutil', '-convert', 'json', '-o', '-', plist_path]))
+
+
+def SavePlist(plist_path, content, format):
+  """Saves |content| as Apple Property List in |format| at |plist_path|."""
+  proc = subprocess.Popen(
+      ['plutil', '-convert', format, '-o', plist_path, '-'],
+      stdin=subprocess.PIPE)
+  output, _ = proc.communicate(json.dumps(content))
+  if proc.returncode:
+    raise subprocess.CalledProcessError(
+        proc.returncode,
+        ['plutil', '-convert', format, '-o', plist_path, '-'],
+        output)
+
+
+def MergeObjects(obj1, obj2):
+  """Merges two objects (either dictionary, list, string or numbers)."""
+  if type(obj1) != type(obj2):
+    return obj2
+
+  if isinstance(obj2, dict):
+    result = dict(obj1)
+    for key in obj2:
+      value1 = obj1.get(key, None)
+      value2 = obj2.get(key, None)
+      result[key] = MergeObjects(value1, value2)
+    return result
+
+  if isinstance(obj2, list):
+    return obj1 + obj2
+
+  return obj2
+
+
+def MergePlists(plist_paths):
+  """Loads and merges all Apple Property List files at |plist_paths|."""
+  plist = {}
+  for plist_path in plist_paths:
+    plist = MergeObjects(plist, LoadPlist(plist_path))
+  return plist
+
+
+def PerformSubstitutions(plist, substitutions):
+  """Performs variables substitutions in |plist| given by |substitutions|."""
+  if isinstance(plist, dict):
+    result = dict(plist)
+    for key in plist:
+      result[key] = PerformSubstitutions(plist[key], substitutions)
+    return result
+
+  if isinstance(plist, list):
+    return [ PerformSubstitutions(item, substitutions) for item in plist ]
+
+  if isinstance(plist, (str, unicode)):
+    result = plist
+    while True:
+      match = VARIABLE_PATTERN.search(result)
+      if not match:
+        break
+
+      extent = match.span()
+      expand = substitutions[match.group(1)]
+      result = result[:extent[0]] + expand + result[extent[1]:]
+    return result
+
+  return plist
+
+
+def PerformSubstitutionsFrom(plist, substitutions_path):
+  """Performs variable substitutions in |plist| from |substitutions_path|."""
+  with open(substitutions_path) as substitutions_file:
+    return PerformSubstitutions(plist, json.load(substitutions_file))
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-s', '--substitutions',
+      help='path to a JSON file containing variable substitutions')
+  parser.add_argument(
+      '-f', '--format', default='json', choices=('json', 'binary1', 'xml1'),
+      help='format of the generated file')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path to the result; - means stdout')
+  parser.add_argument(
+      'inputs', nargs='+',
+      help='path of the input files to merge')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  data = MergePlists(args.inputs)
+  if args.substitutions:
+    data = PerformSubstitutionsFrom(
+        data, args.substitutions)
+
+  SavePlist(args.output, data, args.format)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/scripts/sdk_info.py b/examples/ios/build/config/ios/scripts/sdk_info.py
new file mode 100644 (file)
index 0000000..827aee0
--- /dev/null
@@ -0,0 +1,147 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Collects information about the SDK and return them as JSON file."""
+
+import argparse
+import json
+import re
+import subprocess
+import sys
+
+# Patterns used to extract the Xcode version and build version.
+XCODE_VERSION_PATTERN = re.compile(r'Xcode (\d+)\.(\d+)')
+XCODE_BUILD_PATTERN = re.compile(r'Build version (.*)')
+
+
+def GetAppleCpuName(target_cpu):
+  """Returns the name of the |target_cpu| using Apple's convention."""
+  return {
+      'x64': 'x86_64',
+      'arm': 'armv7',
+      'x86': 'i386'
+  }.get(target_cpu, target_cpu)
+
+
+def IsSimulator(target_cpu):
+  """Returns whether the |target_cpu| corresponds to a simulator build."""
+  return not target_cpu.startswith('arm')
+
+
+def GetPlatform(target_cpu):
+  """Returns the platform for the |target_cpu|."""
+  if IsSimulator(target_cpu):
+    return 'iphonesimulator'
+  else:
+    return 'iphoneos'
+
+
+def GetPlaformDisplayName(target_cpu):
+  """Returns the platform display name for the |target_cpu|."""
+  if IsSimulator(target_cpu):
+    return 'iPhoneSimulator'
+  else:
+    return 'iPhoneOS'
+
+
+def ExtractOSVersion():
+  """Extract the version of macOS of the current machine."""
+  return subprocess.check_output(['sw_vers', '-buildVersion']).strip()
+
+
+def ExtractXcodeInfo():
+  """Extract Xcode version and build version."""
+  version, build = None, None
+  for line in subprocess.check_output(['xcodebuild', '-version']).splitlines():
+    match = XCODE_VERSION_PATTERN.search(line)
+    if match:
+      major, minor = match.group(1), match.group(2)
+      version = major.rjust(2, '0') + minor.ljust(2, '0')
+      continue
+
+    match = XCODE_BUILD_PATTERN.search(line)
+    if match:
+      build = match.group(1)
+      continue
+
+  assert version is not None and build is not None
+  return version, build
+
+
+def ExtractSDKInfo(info, sdk):
+  """Extract information about the SDK."""
+  return subprocess.check_output(
+      ['xcrun', '--sdk', sdk, '--show-sdk-' + info]).strip()
+
+
+def GetSDKInfoForCpu(target_cpu, sdk_version, deployment_target):
+  """Returns a dictionary with information about the SDK."""
+  platform = GetPlatform(target_cpu)
+  sdk_version = sdk_version or ExtractSDKInfo('version', platform)
+  deployment_target = deployment_target or sdk_version
+
+  target = target_cpu + '-apple-ios' + deployment_target
+  if IsSimulator(target_cpu):
+    target = target + '-simulator'
+
+  xcode_version, xcode_build = ExtractXcodeInfo()
+  effective_sdk = platform + sdk_version
+
+  sdk_info = {}
+  sdk_info['compiler'] = 'com.apple.compilers.llvm.clang.1_0'
+  sdk_info['is_simulator'] = IsSimulator(target_cpu)
+  sdk_info['macos_build'] = ExtractOSVersion()
+  sdk_info['platform'] = platform
+  sdk_info['platform_name'] = GetPlaformDisplayName(target_cpu)
+  sdk_info['sdk'] = effective_sdk
+  sdk_info['sdk_build'] = ExtractSDKInfo('build-version', effective_sdk)
+  sdk_info['sdk_path'] = ExtractSDKInfo('path', effective_sdk)
+  sdk_info['sdk_version'] = sdk_version
+  sdk_info['target'] = target
+  sdk_info['xcode_build'] = xcode_build
+  sdk_info['xcode_version'] = xcode_version
+
+  return sdk_info
+
+
+def ParseArgs(argv):
+  """Parses command line arguments."""
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+  parser.add_argument(
+      '-t', '--target-cpu', default='x64',
+      choices=('x86', 'x64', 'arm', 'arm64'),
+      help='target cpu')
+  parser.add_argument(
+      '-s', '--sdk-version',
+      help='version of the sdk')
+  parser.add_argument(
+      '-d', '--deployment-target',
+      help='iOS deployment target')
+  parser.add_argument(
+      '-o', '--output', default='-',
+      help='path of the output file to create; - means stdout')
+
+  return parser.parse_args(argv)
+
+
+def main(argv):
+  args = ParseArgs(argv)
+
+  sdk_info = GetSDKInfoForCpu(
+      GetAppleCpuName(args.target_cpu),
+      args.sdk_version,
+      args.deployment_target)
+
+  if args.output == '-':
+    sys.stdout.write(json.dumps(sdk_info))
+  else:
+    with open(args.output, 'w') as output:
+      output.write(json.dumps(sdk_info))
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/config/ios/sdk_info.gni b/examples/ios/build/config/ios/sdk_info.gni
new file mode 100644 (file)
index 0000000..90b8631
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/deployment_target.gni")
+
+sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+                       [
+                         "--target-cpu",
+                         current_cpu,
+                         "--deployment-target",
+                         ios_deployment_target,
+                       ],
+                       "json")
diff --git a/examples/ios/build/config/ios/templates/ios_app_bundle.gni b/examples/ios/build/config/ios/templates/ios_app_bundle.gni
new file mode 100644 (file)
index 0000000..a82a5fa
--- /dev/null
@@ -0,0 +1,147 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/bundle_identifier_prefix.gni")
+import("//build/config/ios/sdk_info.gni")
+import("//build/config/ios/templates/ios_binary_bundle.gni")
+import("//build/config/ios/templates/merge_plist.gni")
+
+# Template to generate an app bundle.
+#
+# All the other parameters are forwarded to a shared_library target that will
+# generate the bundle binary. In general, you want to pass at least "sources"
+# or "deps" to have some binary objects included in your shared library.
+#
+# Arguments
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+#   - bundle_identifier_prefix (optional)
+#
+#       prefix for the bundle identifier (the full identifier will be defined
+#       to $bundle_identifier_prefix.$output_name); if unset will defaults to
+#       default_bundle_identifier_prefix
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; defaults to $target_name
+#
+template("ios_app_bundle") {
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _bundle_identifier_prefix = default_bundle_identifier_prefix
+  if (defined(invoker.bundle_identifier_prefix)) {
+    _bundle_identifier_prefix = invoker.bundle_identifier_prefix
+  }
+
+  _bundle_identifier = "$_bundle_identifier_prefix.$_output_name"
+
+  _app_prefix_target = target_name + "_app_prefix"
+  _app_prefix_output = "$target_out_dir/$_app_prefix_target/app_prefix.json"
+
+  action(_app_prefix_target) {
+    script = "//build/config/ios/scripts/find_app_identifier_prefix.py"
+    sources = []
+    outputs = [ _app_prefix_output ]
+    args = [
+      "-b=" + _bundle_identifier,
+      "-o=" + rebase_path(_app_prefix_output, root_build_dir),
+    ]
+  }
+
+  if (sdk_info.is_simulator) {
+    _simu_xcent_target = target_name + "_simu_xcent"
+    _simu_xcent_output =
+        "$target_out_dir/$_simu_xcent_target/" + "Entitlements-Simulated.plist"
+
+    merge_plist(_simu_xcent_target) {
+      format = "xml1"
+      output = _simu_xcent_output
+      plists = [ "//build/config/ios/resources/Entitlements-Simulated.plist" ]
+      substitutions_json = _app_prefix_output
+      deps = [ ":$_app_prefix_target" ]
+    }
+  }
+
+  _executable_target = target_name + "_executable"
+  _executable_bundle = target_name + "_executable_bundle"
+
+  executable(_executable_target) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "display_name",
+                             "info_plist",
+                             "output_name",
+                             "public_headers",
+                           ])
+
+    output_extension = ""
+    output_name = _output_name
+    output_prefix_override = true
+    output_dir = "$target_out_dir/$_executable_target"
+
+    if (sdk_info.is_simulator) {
+      if (!defined(deps)) {
+        deps = []
+      }
+      if (!defined(inputs)) {
+        inputs = []
+      }
+      if (!defined(ldflags)) {
+        ldflags = []
+      }
+
+      deps += [ ":$_simu_xcent_target" ]
+      inputs += [ _simu_xcent_output ]
+      ldflags += [
+        "-Xlinker",
+        "-sectcreate",
+        "-Xlinker",
+        "__TEXT",
+        "-Xlinker",
+        "__entitlements",
+        "-Xlinker",
+        rebase_path(_simu_xcent_output, root_build_dir),
+      ]
+    }
+  }
+
+  bundle_data(_executable_bundle) {
+    public_deps = [ ":$_executable_target" ]
+    sources = [ "$target_out_dir/$_executable_target/$_output_name" ]
+    outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
+  }
+
+  ios_binary_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "deps",
+                             "output_name",
+                             "public_deps",
+                             "public_headers",
+                           ])
+
+    output_name = _output_name
+    product_type = "com.apple.product-type.application"
+
+    bundle_identifier = _bundle_identifier
+    bundle_extension = "app"
+    bundle_type = "AAPL"
+
+    public_deps = [ ":$_executable_bundle" ]
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/ios_binary_bundle.gni b/examples/ios/build/config/ios/templates/ios_binary_bundle.gni
new file mode 100644 (file)
index 0000000..1a3d629
--- /dev/null
@@ -0,0 +1,121 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/templates/merge_plist.gni")
+
+# Template to create an Apple bundle containing a binary file (e.g. .app or
+# .framework bundle).
+#
+# Arguments
+#
+#   - bundle_extension
+#
+#       extension of the bundle (e.g. "app", "framework", ...); must not
+#       include the dot preceding the extension
+#
+#   - bundle_type
+#
+#       four letter code corresponding to the bundle type ("FMWK", "AAPL",
+#       ...); used to fill the "Bundle OS Type code" value in the generated
+#       Info.plist for the bundle
+#
+#   - bundle_identitier
+#
+#       bundle identitifier
+#
+#   - product_type
+#
+#       type of the generated bundle (used for Xcode project)
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; the bundle binary (i.e.
+#       the application or the library) must have the same name; defaults
+#       to $target_name
+#
+#   - display_name (optional)
+#
+#       display name of the bundle (e.g. the name that is displayed to the
+#       user); defaults to $output_name
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+template("ios_binary_bundle") {
+  assert(
+      defined(invoker.bundle_extension),
+      "bundle_extension must be defined for ios_binary_bundle ($target_name)")
+  assert(
+      defined(invoker.bundle_identifier),
+      "bundle_identifier must be defined for ios_binary_bundle ($target_name)")
+  assert(defined(invoker.bundle_type),
+         "bundle_type must be defined for ios_binary_bundle ($target_name)")
+  assert(defined(invoker.product_type),
+         "product_type must be defined for ios_binary_bundle ($target_name)")
+
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _display_name = _output_name
+  if (defined(invoker.display_name)) {
+    _display_name = invoker.display_name
+  }
+
+  _plist_target = target_name + "_plist"
+  _plist_bundle = target_name + "_plist_bundle"
+
+  merge_plist(_plist_target) {
+    substitutions = {
+      CURRENT_PROJECT_VERSION = "1"
+      DEVELOPMENT_LANGUAGE = "en"
+      EXECUTABLE_NAME = "$_output_name"
+      PRODUCT_BUNDLE_IDENTIFIER = invoker.bundle_identifier
+      PRODUCT_BUNDLE_PACKAGE_TYPE = invoker.bundle_type
+      PRODUCT_NAME = "$_display_name"
+    }
+
+    format = "binary1"
+    output = "$target_out_dir/$_plist_target/Info.plist"
+    plists = [
+      get_label_info("//build/config/ios:compiler_plist", "target_out_dir") +
+          "/compiler_plist/Info.plist",
+      "//build/config/ios/resources/Info.plist",
+    ]
+
+    if (defined(invoker.info_plist)) {
+      plists += [ invoker.info_plist ]
+    }
+
+    deps = [ "//build/config/ios:compiler_plist" ]
+  }
+
+  bundle_data(_plist_bundle) {
+    public_deps = [ ":$_plist_target" ]
+    sources = [ "$target_out_dir/$_plist_target/Info.plist" ]
+    outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
+  }
+
+  create_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "display_name",
+                             "output_name",
+                             "bundle_extension",
+                             "bundle_type",
+                           ])
+
+    if (!defined(public_deps)) {
+      public_deps = []
+    }
+    public_deps += [ ":$_plist_bundle" ]
+    bundle_root_dir = "$root_out_dir/$_output_name.${invoker.bundle_extension}"
+    bundle_contents_dir = bundle_root_dir
+    bundle_executable_dir = bundle_contents_dir
+    bundle_resources_dir = bundle_contents_dir
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/ios_framework_bundle.gni b/examples/ios/build/config/ios/templates/ios_framework_bundle.gni
new file mode 100644 (file)
index 0000000..13db266
--- /dev/null
@@ -0,0 +1,152 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/bundle_identifier_prefix.gni")
+import("//build/config/ios/templates/ios_binary_bundle.gni")
+
+# Template to generate a framework bundle.
+#
+# All the other parameters are forwarded to a shared_library target that will
+# generate the bundle binary. In general, you want to pass at least "sources"
+# or "deps" to have some binary objects included in your shared library.
+#
+# Arguments
+#
+#   - info_plist (optional)
+#
+#       path to additional Info.plist to merge into the final bundle Info.plist
+#
+#   - bundle_identifier_prefix (optional)
+#
+#       prefix for the bundle identifier (the full identifier will be defined
+#       to $bundle_identifier_prefix.$output_name); if unset will defaults to
+#       default_bundle_identifier_prefix
+#
+#   - output_name (optional)
+#
+#       name of the bundle without the extension; defaults to $target_name
+#
+#   - public_headers (optional)
+#
+#       list of public headers files to copy into the framework bundle; this
+#       does not generate an umbrella header; an umbrella header named after
+#       the framework bundle will be created
+#
+template("ios_framework_bundle") {
+  _output_name = target_name
+  if (defined(invoker.output_name)) {
+    _output_name = invoker.output_name
+  }
+
+  _dylib_target = target_name + "_dylib"
+  _dylib_bundle = target_name + "_dylib_bundle"
+
+  _bundle_identifier_prefix = default_bundle_identifier_prefix
+  if (defined(invoker.bundle_identifier_prefix)) {
+    _bundle_identifier_prefix = invoker.bundle_identifier_prefix
+  }
+
+  _bundle_identifier = "$_bundle_identifier_prefix.$_output_name"
+
+  shared_library(_dylib_target) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_identifier_prefix",
+                             "bundle_type",
+                             "display_name",
+                             "info_plist",
+                             "output_name",
+                             "public_headers",
+                           ])
+
+    output_extension = ""
+    output_name = _output_name
+    output_prefix_override = true
+    output_dir = "$target_out_dir/$_dylib_target"
+
+    if (!defined(ldflags)) {
+      ldflags = []
+    }
+    ldflags += [
+      "-Xlinker",
+      "-install_name",
+      "-Xlinker",
+      "@rpath/$_output_name.framework/$_output_name",
+    ]
+  }
+
+  bundle_data(_dylib_bundle) {
+    public_deps = [ ":$_dylib_target" ]
+    sources = [ "$target_out_dir/$_dylib_target/$_output_name" ]
+    outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
+  }
+
+  if (defined(invoker.public_headers)) {
+    _umbrella_target = target_name + "_umbrella"
+    _umbrella_output = "$target_out_dir/$_umbrella_target/$_output_name.h"
+
+    action(_umbrella_target) {
+      script = "//build/config/ios/scripts/generate_umbrella_header.py"
+      sources = []
+      outputs = [ _umbrella_output ]
+      args = [ "-o=" + rebase_path(_umbrella_output, root_build_dir) ] +
+             rebase_path(invoker.public_headers, root_build_dir)
+    }
+
+    _headers_bundle = target_name + "_headers_bundle"
+
+    bundle_data(_headers_bundle) {
+      sources = invoker.public_headers + [ _umbrella_output ]
+      outputs = [ "{{bundle_resources_dir}}/Headers/{{source_file_part}}" ]
+      public_deps = [ ":$_umbrella_target" ]
+    }
+  }
+
+  _config_name = target_name + "_config"
+
+  config(_config_name) {
+    framework_dirs = [ root_out_dir ]
+    frameworks = [ "$_output_name.framework" ]
+  }
+
+  ios_binary_bundle(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "bundle_extension",
+                             "bundle_type",
+                             "configs",
+                             "deps",
+                             "output_name",
+                             "public_configs",
+                             "public_deps",
+                             "public_headers",
+                           ])
+
+    output_name = _output_name
+    product_type = "com.apple.product-type.framework"
+
+    bundle_identifier = _bundle_identifier
+    bundle_extension = "framework"
+    bundle_type = "FMWK"
+
+    public_deps = [ ":$_dylib_bundle" ]
+
+    if (defined(invoker.public_headers)) {
+      public_deps += [ ":$_headers_bundle" ]
+    }
+
+    public_configs = [ ":$_config_name" ]
+  }
+
+  _target_name = target_name
+
+  bundle_data("$target_name+bundle") {
+    public_deps = [ ":$_target_name" ]
+    sources = [ "$root_out_dir/$_output_name.framework" ]
+    outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/merge_plist.gni b/examples/ios/build/config/ios/templates/merge_plist.gni
new file mode 100644 (file)
index 0000000..f48432b
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Template to merge multiple Apple Property List file into a single file.
+#
+# Arguments
+#
+#   - output
+#
+#       path of the file that will be generated (must be in a sub-directory
+#       of root_build_dir)
+#
+#   - plists
+#
+#       list of path to Apple Property List file to merge (the file may be
+#       in either "json", "binary1" or "xml1" format)
+#
+#   - format (optional)
+#
+#       format in which the file must be saved; must be one of "json",
+#       "binary1" or "xml1" (default to "json" if omitted)
+#
+#   - substitutions (optional)
+#
+#       a scope defining variable substitutions to perform when merging the
+#       Property List files (i.e. if scope define foo = "bar", occurences
+#       of $(foo) in any string in a property list will be replaced by
+#       bar)
+#
+template("merge_plist") {
+  assert(defined(invoker.output) && invoker.output != "",
+         "output must be defined for merge_plist ($target_name)")
+  assert(defined(invoker.plists) && invoker.plists != [],
+         "plists must be defined for merge_plist ($target_name)")
+
+  if (defined(invoker.substitutions)) {
+    assert(!defined(invoker.substitutions_json),
+           "cannot define both substitutions and substitutions_json")
+
+    _substitutions_json = "$target_out_dir/$target_name/substitutions.json"
+    write_file(_substitutions_json, invoker.substitutions, "json")
+  }
+
+  if (defined(invoker.substitutions_json)) {
+    _substitutions_json = invoker.substitutions_json
+  }
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "args",
+                             "format",
+                             "inputs",
+                             "output",
+                             "plists",
+                             "script",
+                             "sources",
+                             "substitutions",
+                             "substitutions_json",
+                           ])
+
+    script = "//build/config/ios/scripts/merge_plist.py"
+    sources = invoker.plists
+    outputs = [ invoker.output ]
+
+    _format = "json"
+    if (defined(invoker.format)) {
+      _format = invoker.format
+    }
+
+    args = [
+      "-f=" + _format,
+      "-o=" + rebase_path(invoker.output, root_build_dir),
+    ]
+
+    if (defined(_substitutions_json)) {
+      inputs = [ _substitutions_json ]
+      args += [ "-s=" + rebase_path(_substitutions_json) ]
+    }
+
+    args += rebase_path(sources, root_build_dir)
+  }
+}
diff --git a/examples/ios/build/config/ios/templates/storyboards.gni b/examples/ios/build/config/ios/templates/storyboards.gni
new file mode 100644 (file)
index 0000000..de36ef3
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/deployment_target.gni")
+
+template("storyboards") {
+  assert(defined(invoker.sources),
+         "sources must be defined for storyboard ($target_name)")
+
+  _compile_target = target_name + "_compile"
+  _compile_output =
+      "$target_out_dir/$_compile_target/{{source_name_part}}.storyboardc"
+
+  action_foreach(_compile_target) {
+    script = "//build/config/ios/scripts/compile_storyboard.py"
+    sources = invoker.sources
+    outputs = [ _compile_output ]
+    args = [
+      "{{source}}",
+      "-o=" + rebase_path(_compile_output, root_build_dir),
+      "--minimum-deployment-target=$ios_deployment_target",
+    ]
+  }
+
+  bundle_data(target_name) {
+    public_deps = [ ":$_compile_target" ]
+    sources = get_target_outputs(":$_compile_target")
+    outputs = [ "{{bundle_root_dir}}/Base.lproj/{{source_file_part}}" ]
+  }
+}
diff --git a/examples/ios/build/toolchain/apple/swiftc.py b/examples/ios/build/toolchain/apple/swiftc.py
new file mode 100755 (executable)
index 0000000..88ae6e5
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/python3
+
+
+import argparse
+import collections
+import json
+import os
+import subprocess
+import sys
+import tempfile
+
+
+class OrderedSet(collections.OrderedDict):
+
+  def add(self, value):
+    self[value] = True
+
+
+def compile_module(module, sources, settings, extras, tmpdir):
+  output_file_map = {}
+  if settings.whole_module_optimization:
+    output_file_map[''] = {
+        'object': os.path.join(settings.object_dir, module + '.o'),
+        'dependencies': os.path.join(tmpdir, module + '.d'),
+    }
+  else:
+    for source in sources:
+      name, _ = os.path.splitext(os.path.basename(source))
+      output_file_map[source] = {
+          'object': os.path.join(settings.object_dir, name + '.o'),
+          'dependencies': os.path.join(tmpdir, name + '.d'),
+      }
+
+  for key in ('module_path', 'header_path', 'depfile'):
+    path = getattr(settings, key)
+    if os.path.exists(path):
+      os.unlink(path)
+    if key == 'module_path':
+      for ext in '.swiftdoc', '.swiftsourceinfo':
+        path = os.path.splitext(getattr(settings, key))[0] + ext
+        if os.path.exists(path):
+          os.unlink(path)
+    directory = os.path.dirname(path)
+    if not os.path.exists(directory):
+      os.makedirs(directory)
+
+  if not os.path.exists(settings.object_dir):
+    os.makedirs(settings.object_dir)
+
+  for key in output_file_map:
+    path = output_file_map[key]['object']
+    if os.path.exists(path):
+      os.unlink(path)
+
+  output_file_map_path = os.path.join(tmpdir, module + '.json')
+  with open(output_file_map_path, 'w') as output_file_map_file:
+    output_file_map_file.write(json.dumps(output_file_map))
+    output_file_map_file.flush()
+
+  extra_args = []
+  if settings.bridge_header:
+    extra_args.extend([
+        '-import-objc-header',
+        os.path.abspath(settings.bridge_header),
+    ])
+
+  if settings.whole_module_optimization:
+    extra_args.append('-whole-module-optimization')
+
+  if settings.target:
+    extra_args.extend([
+        '-target',
+        settings.target,
+    ])
+
+  if settings.sdk:
+    extra_args.extend([
+        '-sdk',
+        os.path.abspath(settings.sdk),
+    ])
+
+  if settings.include_dirs:
+    for include_dir in settings.include_dirs:
+      extra_args.append('-I' + include_dir)
+
+  process = subprocess.Popen(
+      ['swiftc',
+       '-parse-as-library',
+       '-module-name',
+       module,
+       '-emit-object',
+       '-emit-dependencies',
+       '-emit-module',
+       '-emit-module-path',
+       settings.module_path,
+       '-emit-objc-header',
+       '-emit-objc-header-path',
+       settings.header_path,
+       '-output-file-map',
+       output_file_map_path,
+      ] + extra_args + extras + sources,
+      stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+      universal_newlines=True)
+
+  stdout, stderr = process.communicate()
+  if process.returncode:
+    sys.stdout.write(stdout)
+    sys.stderr.write(stderr)
+    sys.exit(process.returncode)
+
+
+  depfile_content = collections.OrderedDict()
+  for key in output_file_map:
+    for line in open(output_file_map[key]['dependencies']):
+      output, inputs = line.split(' : ', 2)
+      _, ext = os.path.splitext(output)
+      if ext == '.o':
+        key = output
+      else:
+        key = os.path.splitext(settings.module_path)[0] + ext
+      if key not in depfile_content:
+        depfile_content[key] = OrderedSet()
+      for path in inputs.split():
+        depfile_content[key].add(path)
+
+  with open(settings.depfile, 'w') as depfile:
+    for key in depfile_content:
+      if not settings.depfile_filter or key in settings.depfile_filter:
+        inputs = depfile_content[key]
+        depfile.write('%s : %s\n' % (key, ' '.join(inputs)))
+
+
+def main(args):
+  parser = argparse.ArgumentParser(add_help=False)
+  parser.add_argument(
+      '--module-name',
+      help='name of the Swift module')
+  parser.add_argument(
+      '--include', '-I', action='append', dest='include_dirs',
+      help='add directory to header search path')
+  parser.add_argument(
+      'sources', nargs='+',
+      help='Swift source file to compile')
+  parser.add_argument(
+      '--whole-module-optimization', action='store_true',
+      help='enable whole module optimization')
+  parser.add_argument(
+      '--object-dir', '-o',
+      help='path to the generated object files directory')
+  parser.add_argument(
+      '--module-path', '-m',
+      help='path to the generated module file')
+  parser.add_argument(
+      '--header-path', '-h',
+      help='path to the generated header file')
+  parser.add_argument(
+      '--bridge-header', '-b',
+      help='path to the Objective-C bridge header')
+  parser.add_argument(
+      '--depfile', '-d',
+      help='path to the generated depfile')
+  parser.add_argument(
+      '--depfile-filter', action='append',
+      help='limit depfile to those files')
+  parser.add_argument(
+      '--target', action='store',
+      help='generate code for the given target <triple>')
+  parser.add_argument(
+      '--sdk', action='store',
+      help='compile against sdk')
+
+  parsed, extras = parser.parse_known_args(args)
+  with tempfile.TemporaryDirectory() as tmpdir:
+    compile_module(
+        parsed.module_name,
+        parsed.sources,
+        parsed,
+        extras,
+        tmpdir)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/examples/ios/build/toolchain/ios/BUILD.gn b/examples/ios/build/toolchain/ios/BUILD.gn
new file mode 100644 (file)
index 0000000..12d246b
--- /dev/null
@@ -0,0 +1,167 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/deployment_target.gni")
+
+declare_args() {
+  # Configure whether whole module optimization is enabled when compiling
+  # swift modules.
+  swift_whole_module_optimization = true
+}
+
+template("ios_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.toolchain_args),
+           "Toolchains must declare toolchain_args")
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+
+    _sdk_info = exec_script("//build/config/ios/scripts/sdk_info.py",
+                            [
+                              "--target-cpu",
+                              current_cpu,
+                              "--deployment-target",
+                              ios_deployment_target,
+                            ],
+                            "json")
+
+    cc = "clang -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
+    cxx = "clang++ -target ${_sdk_info.target} -isysroot ${_sdk_info.sdk_path}"
+
+    swiftmodule_switch = "-Wl,-add_ast_path,"
+
+    tool("link") {
+      output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = output + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [ output ]
+      command = "$cxx {{ldflags}} -o $output -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}} {{swiftmodules}}"
+      description = "LINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ""
+      output_prefix = ""
+    }
+
+    tool("solink") {
+      dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = dylib + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [ dylib ]
+      command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}} {{swiftmodules}}"
+      description = "SOLINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ".dylib"
+      output_prefix = "lib"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("objc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJC {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("objcxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJCXX {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy_bundle_data") {
+      command = "rm -rf {{output}} && cp -PR {{source}} {{output}}"
+      description = "COPY_BUNDLE_DATA {{output}}"
+    }
+
+    tool("swift") {
+      depfile = "{{target_out_dir}}/{{module_name}}.d"
+      depsformat = "gcc"
+
+      outputs = [
+        # The module needs to be the first output to ensure the
+        # depfile generate works correctly with ninja < 1.9.0.
+        "{{target_gen_dir}}/{{module_name}}.swiftmodule",
+
+        "{{target_gen_dir}}/{{module_name}}.h",
+        "{{target_gen_dir}}/{{module_name}}.swiftdoc",
+        "{{target_gen_dir}}/{{module_name}}.swiftsourceinfo",
+      ]
+
+      if (swift_whole_module_optimization) {
+        _extra_flag = "--whole-module-optimization"
+        _object_dir = "{{target_out_dir}}"
+        outputs += [ "{{target_out_dir}}/{{module_name}}.o" ]
+      } else {
+        _extra_flag = ""
+        _object_dir = "{{target_out_dir}}/{{label_name}}"
+        partial_outputs =
+            [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+      }
+
+      _swiftc = rebase_path("//build/toolchain/apple/swiftc.py", root_build_dir)
+      command = "$_swiftc --target ${_sdk_info.target} --sdk ${_sdk_info.sdk_path} --module-name {{module_name}} --object-dir $_object_dir --module-path {{target_gen_dir}}/{{module_name}}.swiftmodule --header-path {{target_gen_dir}}/{{module_name}}.h --depfile {{target_out_dir}}/{{module_name}}.d --depfile-filter {{target_gen_dir}}/{{module_name}}.swiftmodule --bridge-header {{bridge_header}} $_extra_flag {{defines}} {{swiftflags}} {{include_dirs}} {{module_dirs}} {{inputs}}"
+    }
+  }
+}
+
+ios_toolchain("clang_x86") {
+  toolchain_args = {
+    current_cpu = "x86"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_x64") {
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_arm") {
+  toolchain_args = {
+    current_cpu = "arm"
+    current_os = "ios"
+  }
+}
+
+ios_toolchain("clang_arm64") {
+  toolchain_args = {
+    current_cpu = "arm64"
+    current_os = "ios"
+  }
+}
diff --git a/examples/ios/build/toolchain/mac/BUILD.gn b/examples/ios/build/toolchain/mac/BUILD.gn
new file mode 100644 (file)
index 0000000..045a90f
--- /dev/null
@@ -0,0 +1,119 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+template("mac_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.toolchain_args),
+           "Toolchains must declare toolchain_args")
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+
+    cc = "clang"
+    cxx = "clang++"
+
+    tool("link") {
+      output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = output + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [ output ]
+      command = "$cxx {{ldflags}} -o $output -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "LINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ""
+      output_prefix = ""
+    }
+
+    tool("solink") {
+      dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = dylib + ".rsp"
+      rspfile_content = "{{inputs_newline}}"
+
+      outputs = [ dylib ]
+      command = "$cxx -dynamiclib {{ldflags}} -o $dylib -Wl,-filelist,$rspfile {{libs}} {{solibs}} {{frameworks}}"
+      description = "SOLINK {{output}}"
+
+      default_output_dir = "{{root_out_dir}}"
+      default_output_extension = ".dylib"
+      output_prefix = "lib"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("objc") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJC {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("objcxx") {
+      depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
+      command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "OBJCXX {{output}}"
+      outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.o" ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy_bundle_data") {
+      command = "rm -rf {{output}} && cp -a {{source}} {{output}}"
+      description = "COPY_BUNDLE_DATA {{output}}"
+    }
+  }
+}
+
+mac_toolchain("clang_x86") {
+  toolchain_args = {
+    current_cpu = "x86"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_x64") {
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_arm") {
+  toolchain_args = {
+    current_cpu = "arm"
+    current_os = "mac"
+  }
+}
+
+mac_toolchain("clang_arm64") {
+  toolchain_args = {
+    current_cpu = "arm64"
+    current_os = "mac"
+  }
+}
diff --git a/examples/ios/host/BUILD.gn b/examples/ios/host/BUILD.gn
new file mode 100644 (file)
index 0000000..90a789f
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (current_toolchain == host_toolchain) {
+  executable("username") {
+    sources = [ "main.cc" ]
+  }
+} else {
+  group("username") {
+    deps = [ ":username($host_toolchain)" ]
+  }
+}
diff --git a/examples/ios/host/main.cc b/examples/ios/host/main.cc
new file mode 100644 (file)
index 0000000..1f244ef
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdlib.h>
+#include <iostream>
+#include <string>
+
+namespace {
+
+// Returns the current user username.
+std::string Username() {
+  const char* username = getenv("USER");
+  return username ? std::string(username) : std::string();
+}
+
+// Writes |string| to |stream| while escaping all C escape sequences.
+void EscapeString(std::ostream* stream, const std::string& string) {
+  for (char c : string) {
+    switch (c) {
+      case 0:
+        *stream << "\\0";
+        break;
+      case '\a':
+        *stream << "\\a";
+        break;
+      case '\b':
+        *stream << "\\b";
+        break;
+      case '\e':
+        *stream << "\\e";
+        break;
+      case '\f':
+        *stream << "\\f";
+        break;
+      case '\n':
+        *stream << "\\n";
+        break;
+      case '\r':
+        *stream << "\\r";
+        break;
+      case '\t':
+        *stream << "\\t";
+        break;
+      case '\v':
+        *stream << "\\v";
+        break;
+      case '\\':
+        *stream << "\\\\";
+        break;
+      case '\"':
+        *stream << "\\\"";
+        break;
+      default:
+        *stream << c;
+        break;
+    }
+  }
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  std::string username = Username();
+
+  std::cout << "{\"username\": \"";
+  EscapeString(&std::cout, username);
+  std::cout << "\"}" << std::endl;
+
+  return 0;
+}
diff --git a/examples/ios/shared/BUILD.gn b/examples/ios/shared/BUILD.gn
new file mode 100644 (file)
index 0000000..f4641c3
--- /dev/null
@@ -0,0 +1,18 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/templates/ios_framework_bundle.gni")
+
+ios_framework_bundle("hello_framework") {
+  output_name = "HelloShared"
+
+  sources = [
+    "hello_shared.h",
+    "hello_shared.m",
+  ]
+  public_headers = [ "hello_shared.h" ]
+
+  defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
+  frameworks = [ "Foundation.framework" ]
+}
diff --git a/examples/ios/shared/hello_shared.h b/examples/ios/shared/hello_shared.h
new file mode 100644 (file)
index 0000000..b351a50
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+
+@interface Greetings : NSObject
+
++ (NSString*)greet;
+
++ (NSString*)greetWithName:(NSString*)name;
+
+@end
diff --git a/examples/ios/shared/hello_shared.m b/examples/ios/shared/hello_shared.m
new file mode 100644 (file)
index 0000000..5e81114
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "hello_shared.h"
+
+@implementation Greetings
+
++ (NSString*)greet {
+  return [NSString stringWithFormat:@"Hello from %@!", [Greetings displayName]];
+}
+
++ (NSString*)greetWithName:(NSString*)name {
+  return [NSString stringWithFormat:@"Hello, %@!", name];
+}
+
++ (NSString*)displayName {
+  NSBundle* bundle = [NSBundle bundleForClass:[Greetings class]];
+  return [[bundle infoDictionary] objectForKey:@"CFBundleName"];
+}
+
+@end
diff --git a/examples/rust_example/.gn b/examples/rust_example/.gn
new file mode 100644 (file)
index 0000000..9fe0b42
--- /dev/null
@@ -0,0 +1 @@
+buildconfig = "//BUILDCONFIG.gn"
diff --git a/examples/rust_example/BUILD.gn b/examples/rust_example/BUILD.gn
new file mode 100644 (file)
index 0000000..964b331
--- /dev/null
@@ -0,0 +1,3 @@
+group("default") {
+  deps = [ "//hello_world/src:hello_world" ]
+}
diff --git a/examples/rust_example/BUILDCONFIG.gn b/examples/rust_example/BUILDCONFIG.gn
new file mode 100644 (file)
index 0000000..62b5bb6
--- /dev/null
@@ -0,0 +1,23 @@
+if (target_os == "") {
+  target_os = host_os
+}
+if (target_cpu == "") {
+  target_cpu = host_cpu
+}
+if (current_cpu == "") {
+  current_cpu = target_cpu
+}
+if (current_os == "") {
+  current_os = target_os
+}
+
+_configs = [ "//build:rust_defaults" ]
+
+set_defaults("executable") {
+  configs = _configs
+}
+set_defaults("rust_library") {
+  configs = _configs
+}
+
+set_default_toolchain("//build:rust")
diff --git a/examples/rust_example/README.txt b/examples/rust_example/README.txt
new file mode 100644 (file)
index 0000000..064e869
--- /dev/null
@@ -0,0 +1,4 @@
+This is an example directory structure that compiles a Rust hello world
+example. It is intended to show how to set up a simple Rust GN build.
+
+Don't miss the ".gn" file in this directory!
diff --git a/examples/rust_example/build/BUILD.gn b/examples/rust_example/build/BUILD.gn
new file mode 100644 (file)
index 0000000..ebfaa64
--- /dev/null
@@ -0,0 +1,47 @@
+toolchain("rust") {
+  tool("rust_bin") {
+    depfile = "{{target_out_dir}}/{{crate_name}}.d"
+    outfile = "{{target_out_dir}}/{{crate_name}}"
+    command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+    description = "RUST $outfile"
+    outputs = [ outfile ]
+  }
+
+  tool("rust_staticlib") {
+    depfile = "{{target_out_dir}}/{{crate_name}}.d"
+    outfile = "{{target_out_dir}}/{{crate_name}}.a"
+    command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+    description = "RUST $outfile"
+    outputs = [ outfile ]
+  }
+
+  tool("rust_rlib") {
+    depfile = "{{target_out_dir}}/{{crate_name}}.d"
+    outfile = "{{target_out_dir}}/lib{{crate_name}}.rlib"
+    command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+    description = "RUST $outfile"
+    outputs = [ outfile ]
+  }
+
+  tool("rust_cdylib") {
+    depfile = "{{target_out_dir}}/{{crate_name}}.d"
+    outfile = "{{target_out_dir}}/lib{{crate_name}}.so"
+    command = "rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} --emit=dep-info=$depfile,link -Z dep-info-omit-d-target {{rustflags}} -o $outfile {{rustdeps}} {{externs}}"
+    description = "RUST $outfile"
+    outputs = [ outfile ]
+  }
+
+  tool("stamp") {
+    command = "touch {{output}}"
+    description = "STAMP {{output}}"
+  }
+
+  tool("copy") {
+    command = "cp -af {{source}} {{output}}"
+    description = "COPY {{source}} {{output}}"
+  }
+}
+
+config("rust_defaults") {
+  rustflags = [ "-Cdebuginfo=2" ]
+}
diff --git a/examples/rust_example/hello_world/bar/src/BUILD.gn b/examples/rust_example/hello_world/bar/src/BUILD.gn
new file mode 100644 (file)
index 0000000..b4eec33
--- /dev/null
@@ -0,0 +1,3 @@
+rust_library("bar") {
+  crate_root = "lib.rs"
+}
diff --git a/examples/rust_example/hello_world/bar/src/lib.rs b/examples/rust_example/hello_world/bar/src/lib.rs
new file mode 100644 (file)
index 0000000..43defe9
--- /dev/null
@@ -0,0 +1,23 @@
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        assert_eq!(2 + 2, 4);
+    }
+}
+
+#[derive(Debug)]
+pub struct Foo {
+    s: &'static str,
+    i: &'static str
+}
+
+impl Foo {
+    pub fn new(s: &'static str) -> Foo {
+        Foo{s: s, i: "bar"}
+    }
+}
+
+pub fn answer() -> i32 {
+  42
+}
\ No newline at end of file
diff --git a/examples/rust_example/hello_world/foo/src/BUILD.gn b/examples/rust_example/hello_world/foo/src/BUILD.gn
new file mode 100644 (file)
index 0000000..7525454
--- /dev/null
@@ -0,0 +1,4 @@
+rust_library("foo") {
+  sources = [ "lib.rs" ]
+  deps = [ "//hello_world/bar/src:bar" ]
+}
diff --git a/examples/rust_example/hello_world/foo/src/lib.rs b/examples/rust_example/hello_world/foo/src/lib.rs
new file mode 100644 (file)
index 0000000..73c615c
--- /dev/null
@@ -0,0 +1,11 @@
+#[derive(Debug)]
+pub struct Foo {
+    s: &'static str,
+    i: &'static str
+}
+
+impl Foo {
+    pub fn new(s: &'static str) -> Foo {
+        Foo{s: s, i: "foo"}
+    }
+}
\ No newline at end of file
diff --git a/examples/rust_example/hello_world/src/BUILD.gn b/examples/rust_example/hello_world/src/BUILD.gn
new file mode 100644 (file)
index 0000000..d50b876
--- /dev/null
@@ -0,0 +1,7 @@
+executable("hello_world") {
+  sources = [ "main.rs" ]
+  deps = [ "//hello_world/foo/src:foo" ]
+  aliased_deps = {
+    baz = "//hello_world/foo/src:foo"
+  }
+}
diff --git a/examples/rust_example/hello_world/src/main.rs b/examples/rust_example/hello_world/src/main.rs
new file mode 100644 (file)
index 0000000..ad70a12
--- /dev/null
@@ -0,0 +1,5 @@
+fn main() {
+    let f = baz::Foo::new("hello");
+    println!("Hello world!");
+    println!("I'm from a dependency: {:?}!", f);
+}
\ No newline at end of file
diff --git a/examples/simple_build/.gn b/examples/simple_build/.gn
new file mode 100644 (file)
index 0000000..e5b6d4a
--- /dev/null
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/BUILDCONFIG.gn"
diff --git a/examples/simple_build/BUILD.gn b/examples/simple_build/BUILD.gn
new file mode 100644 (file)
index 0000000..965fc42
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+executable("hello") {
+  sources = [ "hello.cc" ]
+
+  deps = [
+    ":hello_shared",
+    ":hello_static",
+  ]
+}
+
+shared_library("hello_shared") {
+  sources = [
+    "hello_shared.cc",
+    "hello_shared.h",
+  ]
+
+  defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
+}
+
+static_library("hello_static") {
+  sources = [
+    "hello_static.cc",
+    "hello_static.h",
+  ]
+}
diff --git a/examples/simple_build/README.md b/examples/simple_build/README.md
new file mode 100644 (file)
index 0000000..78b3917
--- /dev/null
@@ -0,0 +1,12 @@
+# GN Simple Build Example
+
+This is an example directory structure that compiles some simple targets using
+gcc. It is intended to show how to set up a simple GN build. It is deliberately
+simplistic so the structure is more clear, and doesn't support everything on
+every platform.
+
+It is recommended that you take this and add on as your requirements indicate.
+You may also want to see the Chromium and Fuchsia build configurations for the
+maximal functionality (the [root README](../../README.md) has links to these).
+
+Don't miss the ".gn" file in this directory which may be hidden on your system!
diff --git a/examples/simple_build/build/BUILD.gn b/examples/simple_build/build/BUILD.gn
new file mode 100644 (file)
index 0000000..cc3f948
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("compiler_defaults") {
+  if (current_os == "linux") {
+    cflags = [
+      "-fPIC",
+      "-pthread",
+    ]
+  }
+}
+
+config("executable_ldconfig") {
+  if (!is_mac) {
+    ldflags = [
+      "-Wl,-rpath=\$ORIGIN/",
+      "-Wl,-rpath-link=",
+    ]
+  }
+}
diff --git a/examples/simple_build/build/BUILDCONFIG.gn b/examples/simple_build/build/BUILDCONFIG.gn
new file mode 100644 (file)
index 0000000..619fa97
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (target_os == "") {
+  target_os = host_os
+}
+if (target_cpu == "") {
+  target_cpu = host_cpu
+}
+if (current_cpu == "") {
+  current_cpu = target_cpu
+}
+if (current_os == "") {
+  current_os = target_os
+}
+
+is_linux = host_os == "linux" && current_os == "linux" && target_os == "linux"
+is_mac = host_os == "mac" && current_os == "mac" && target_os == "mac"
+
+# All binary targets will get this list of configs by default.
+_shared_binary_target_configs = [ "//build:compiler_defaults" ]
+
+# Apply that default list to the binary target types.
+set_defaults("executable") {
+  configs = _shared_binary_target_configs
+
+  # Executables get this additional configuration.
+  configs += [ "//build:executable_ldconfig" ]
+}
+set_defaults("static_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("shared_library") {
+  configs = _shared_binary_target_configs
+}
+set_defaults("source_set") {
+  configs = _shared_binary_target_configs
+}
+
+set_default_toolchain("//build/toolchain:gcc")
diff --git a/examples/simple_build/build/toolchain/BUILD.gn b/examples/simple_build/build/toolchain/BUILD.gn
new file mode 100644 (file)
index 0000000..c04ee1e
--- /dev/null
@@ -0,0 +1,88 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+toolchain("gcc") {
+  tool("cc") {
+    depfile = "{{output}}.d"
+    command = "gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+    depsformat = "gcc"
+    description = "CC {{output}}"
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
+  }
+
+  tool("cxx") {
+    depfile = "{{output}}.d"
+    command = "g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+    depsformat = "gcc"
+    description = "CXX {{output}}"
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
+  }
+
+  tool("alink") {
+    command = "rm -f {{output}} && ar rcs {{output}} {{inputs}}"
+    description = "AR {{target_output_name}}{{output_extension}}"
+
+    outputs =
+        [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+    default_output_extension = ".a"
+    output_prefix = "lib"
+  }
+
+  tool("solink") {
+    soname = "{{target_output_name}}{{output_extension}}"  # e.g. "libfoo.so".
+    sofile = "{{output_dir}}/$soname"
+    rspfile = soname + ".rsp"
+    if (is_mac) {
+      os_specific_option = "-install_name @executable_path/$sofile"
+      rspfile_content = "{{inputs}} {{solibs}} {{libs}}"
+    } else {
+      os_specific_option = "-Wl,-soname=$soname"
+      rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+    }
+
+    command = "g++ -shared {{ldflags}} -o $sofile $os_specific_option @$rspfile"
+
+    description = "SOLINK $soname"
+
+    # Use this for {{output_extension}} expansions unless a target manually
+    # overrides it (in which case {{output_extension}} will be what the target
+    # specifies).
+    default_output_extension = ".so"
+
+    # Use this for {{output_dir}} expansions unless a target manually overrides
+    # it (in which case {{output_dir}} will be what the target specifies).
+    default_output_dir = "{{root_out_dir}}"
+
+    outputs = [ sofile ]
+    link_output = sofile
+    depend_output = sofile
+    output_prefix = "lib"
+  }
+
+  tool("link") {
+    outfile = "{{target_output_name}}{{output_extension}}"
+    rspfile = "$outfile.rsp"
+    if (is_mac) {
+      command = "g++ {{ldflags}} -o $outfile @$rspfile {{solibs}} {{libs}}"
+    } else {
+      command = "g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
+    }
+    description = "LINK $outfile"
+    default_output_dir = "{{root_out_dir}}"
+    rspfile_content = "{{inputs}}"
+    outputs = [ outfile ]
+  }
+
+  tool("stamp") {
+    command = "touch {{output}}"
+    description = "STAMP {{output}}"
+  }
+
+  tool("copy") {
+    command = "cp -af {{source}} {{output}}"
+    description = "COPY {{source}} {{output}}"
+  }
+}
diff --git a/examples/simple_build/hello.cc b/examples/simple_build/hello.cc
new file mode 100644 (file)
index 0000000..c4aa448
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+
+#include "hello_shared.h"
+#include "hello_static.h"
+
+int main(int argc, char* argv[]) {
+  printf("%s, %s\n", GetStaticText(), GetSharedText());
+  return 0;
+}
diff --git a/examples/simple_build/hello_shared.cc b/examples/simple_build/hello_shared.cc
new file mode 100644 (file)
index 0000000..58be84c
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hello_shared.h"
+
+const char* GetSharedText() {
+  return "world";
+}
diff --git a/examples/simple_build/hello_shared.h b/examples/simple_build/hello_shared.h
new file mode 100644 (file)
index 0000000..7af804b
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_EXAMPLE_HELLO_SHARED_H_
+#define TOOLS_GN_EXAMPLE_HELLO_SHARED_H_
+
+#if defined(WIN32)
+
+#if defined(HELLO_SHARED_IMPLEMENTATION)
+#define HELLO_EXPORT __declspec(dllexport)
+#define HELLO_EXPORT_PRIVATE __declspec(dllexport)
+#else
+#define HELLO_EXPORT __declspec(dllimport)
+#define HELLO_EXPORT_PRIVATE __declspec(dllimport)
+#endif  // defined(HELLO_SHARED_IMPLEMENTATION)
+
+#else
+
+#if defined(HELLO_SHARED_IMPLEMENTATION)
+#define HELLO_EXPORT __attribute__((visibility("default")))
+#define HELLO_EXPORT_PRIVATE __attribute__((visibility("default")))
+#else
+#define HELLO_EXPORT
+#define HELLO_EXPORT_PRIVATE
+#endif  // defined(HELLO_SHARED_IMPLEMENTATION)
+
+#endif
+
+HELLO_EXPORT const char* GetSharedText();
+
+#endif  // TOOLS_GN_EXAMPLE_HELLO_SHARED_H_
diff --git a/examples/simple_build/hello_static.cc b/examples/simple_build/hello_static.cc
new file mode 100644 (file)
index 0000000..cdf4e67
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hello_static.h"
+
+const char* GetStaticText() {
+  return "Hello";
+}
diff --git a/examples/simple_build/hello_static.h b/examples/simple_build/hello_static.h
new file mode 100644 (file)
index 0000000..f15a633
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_EXAMPLE_HELLO_STATIC_H_
+#define TOOLS_GN_EXAMPLE_HELLO_STATIC_H_
+
+const char* GetStaticText();
+
+#endif  // TOOLS_GN_EXAMPLE_HELLO_STATIC_H_
diff --git a/examples/simple_build/tutorial/README.md b/examples/simple_build/tutorial/README.md
new file mode 100644 (file)
index 0000000..3dd11ef
--- /dev/null
@@ -0,0 +1,4 @@
+# Tutorial directory
+
+This directory isn't used by the simple build example by default. It's here to
+be used for the example in the [quick start guide](../../../docs/quick_start.md).
diff --git a/examples/simple_build/tutorial/tutorial.cc b/examples/simple_build/tutorial/tutorial.cc
new file mode 100644 (file)
index 0000000..b72ffa8
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+
+int main(int argc, char* argv[]) {
+  printf("Hello from the tutorial.\n");
+  return 0;
+}
diff --git a/infra/README.recipes.md b/infra/README.recipes.md
new file mode 100644 (file)
index 0000000..02e4098
--- /dev/null
@@ -0,0 +1,124 @@
+<!--- AUTOGENERATED BY `./recipes.py test train` -->
+# Repo documentation for [gn]()
+## Table of Contents
+
+**[Recipe Modules](#Recipe-Modules)**
+  * [macos_sdk](#recipe_modules-macos_sdk) &mdash; The `macos_sdk` module provides safe functions to access a semi-hermetic XCode installation.
+  * [target](#recipe_modules-target)
+  * [windows_sdk](#recipe_modules-windows_sdk)
+
+**[Recipes](#Recipes)**
+  * [gn](#recipes-gn) &mdash; Recipe for building GN.
+  * [macos_sdk:examples/full](#recipes-macos_sdk_examples_full)
+  * [target:examples/full](#recipes-target_examples_full)
+  * [windows_sdk:examples/full](#recipes-windows_sdk_examples_full)
+## Recipe Modules
+
+### *recipe_modules* / [macos\_sdk](/infra/recipe_modules/macos_sdk)
+
+[DEPS](/infra/recipe_modules/macos_sdk/__init__.py#5): [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+The `macos_sdk` module provides safe functions to access a semi-hermetic
+XCode installation.
+
+Available only to Google-run bots.
+
+#### **class [MacOSSDKApi](/infra/recipe_modules/macos_sdk/api.py#14)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
+
+API for using OS X SDK distributed via CIPD.
+
+&emsp; **@contextmanager**<br>&mdash; **def [\_\_call\_\_](/infra/recipe_modules/macos_sdk/api.py#30)(self):**
+
+Sets up the XCode SDK environment.
+
+This call is a no-op on non-Mac platforms.
+
+This will deploy the helper tool and the XCode.app bundle at
+`[START_DIR]/cache/macos_sdk`.
+
+To avoid machines rebuilding these on every run, set up a named cache in
+your cr-buildbucket.cfg file like:
+
+    caches: {
+      # Cache for mac_toolchain tool and XCode.app
+      name: "macos_sdk"
+      path: "macos_sdk"
+    }
+
+If you have builders which e.g. use a non-current SDK, you can give them
+a uniqely named cache:
+
+    caches: {
+      # Cache for N-1 version mac_toolchain tool and XCode.app
+      name: "macos_sdk_old"
+      path: "macos_sdk"
+    }
+
+Usage:
+  with api.macos_sdk():
+    # sdk with mac build bits
+
+Raises:
+    StepFailure or InfraFailure.
+
+&emsp; **@property**<br>&mdash; **def [sdk\_dir](/infra/recipe_modules/macos_sdk/api.py#25)(self):**
+### *recipe_modules* / [target](/infra/recipe_modules/target)
+
+[DEPS](/infra/recipe_modules/target/__init__.py#5): [recipe\_engine/platform][recipe_engine/recipe_modules/platform]
+
+#### **class [TargetApi](/infra/recipe_modules/target/api.py#82)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
+
+&emsp; **@property**<br>&mdash; **def [host](/infra/recipe_modules/target/api.py#87)(self):**
+### *recipe_modules* / [windows\_sdk](/infra/recipe_modules/windows_sdk)
+
+[DEPS](/infra/recipe_modules/windows_sdk/__init__.py#5): [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+#### **class [WindowsSDKApi](/infra/recipe_modules/windows_sdk/api.py#10)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
+
+API for using Windows SDK distributed via CIPD.
+
+&emsp; **@contextmanager**<br>&mdash; **def [\_\_call\_\_](/infra/recipe_modules/windows_sdk/api.py#19)(self):**
+
+Setups the Windows SDK environment.
+
+This call is a no-op on non-Windows platforms.
+
+Raises:
+    StepFailure or InfraFailure.
+## Recipes
+
+### *recipes* / [gn](/infra/recipes/gn.py)
+
+[DEPS](/infra/recipes/gn.py#8): [macos\_sdk](#recipe_modules-macos_sdk), [target](#recipe_modules-target), [windows\_sdk](#recipe_modules-windows_sdk), [recipe\_engine/buildbucket][recipe_engine/recipe_modules/buildbucket], [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/file][recipe_engine/recipe_modules/file], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/python][recipe_engine/recipe_modules/python], [recipe\_engine/raw\_io][recipe_engine/recipe_modules/raw_io], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+Recipe for building GN.
+
+&mdash; **def [RunSteps](/infra/recipes/gn.py#102)(api, repository):**
+### *recipes* / [macos\_sdk:examples/full](/infra/recipe_modules/macos_sdk/examples/full.py)
+
+[DEPS](/infra/recipe_modules/macos_sdk/examples/full.py#5): [macos\_sdk](#recipe_modules-macos_sdk), [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+&mdash; **def [RunSteps](/infra/recipe_modules/macos_sdk/examples/full.py#13)(api):**
+### *recipes* / [target:examples/full](/infra/recipe_modules/target/examples/full.py)
+
+[DEPS](/infra/recipe_modules/target/examples/full.py#5): [target](#recipe_modules-target), [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+&mdash; **def [RunSteps](/infra/recipe_modules/target/examples/full.py#13)(api):**
+### *recipes* / [windows\_sdk:examples/full](/infra/recipe_modules/windows_sdk/examples/full.py)
+
+[DEPS](/infra/recipe_modules/windows_sdk/examples/full.py#5): [windows\_sdk](#recipe_modules-windows_sdk), [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
+
+&mdash; **def [RunSteps](/infra/recipe_modules/windows_sdk/examples/full.py#13)(api):**
+
+[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-buildbucket
+[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-cipd
+[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-context
+[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-file
+[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-json
+[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-path
+[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-platform
+[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-properties
+[recipe_engine/recipe_modules/python]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-python
+[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-raw_io
+[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/README.recipes.md#recipe_modules-step
+[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/0589a429cf3c164004dae4ced4c75784a50afd81/recipe_engine/recipe_api.py#838
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
new file mode 100644 (file)
index 0000000..77f7e99
--- /dev/null
@@ -0,0 +1,12 @@
+{
+  "api_version": 2,
+  "deps": {
+    "recipe_engine": {
+      "branch": "master",
+      "revision": "d31ba13ede8c21e60116ae61e4490d53ba77fcbd",
+      "url": "https://chromium.googlesource.com/infra/luci/recipes-py"
+    }
+  },
+  "project_id": "gn",
+  "recipes_path": "infra"
+}
diff --git a/infra/config/refs.cfg b/infra/config/refs.cfg
new file mode 100644 (file)
index 0000000..465672f
--- /dev/null
@@ -0,0 +1,8 @@
+# See RefsCfg message in
+# http://luci-config.appspot.com/schemas/projects:refs.cfg for the
+# documentation of this file format.
+
+refs {
+   name: "refs/heads/master"
+   config_path: "infra/config"
+}
diff --git a/infra/recipe_modules/macos_sdk/__init__.py b/infra/recipe_modules/macos_sdk/__init__.py
new file mode 100644 (file)
index 0000000..56b6a94
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'recipe_engine/cipd',
+    'recipe_engine/context',
+    'recipe_engine/json',
+    'recipe_engine/path',
+    'recipe_engine/platform',
+    'recipe_engine/step',
+]
+
+from recipe_engine.recipe_api import Property
+from recipe_engine.config import ConfigGroup, Single
+
+PROPERTIES = {
+    '$gn/macos_sdk':
+        Property(
+            help='Properties specifically for the macos_sdk module.',
+            param_name='sdk_properties',
+            kind=ConfigGroup(  # pylint: disable=line-too-long
+                # XCode build version number. Internally maps to an XCode build id like
+                # '9c40b'. See
+                #
+                #   https://chrome-infra-packages.appspot.com/p/infra_internal/ios/xcode/mac/+/
+                #
+                # For an up to date list of the latest SDK builds.
+                sdk_version=Single(str),
+
+                # The CIPD toolchain tool package and version.
+                tool_pkg=Single(str),
+                tool_ver=Single(str),
+            ),
+            default={
+                'sdk_version':
+                    '12B5025f',
+                'tool_package':
+                    'infra/tools/mac_toolchain/${platform}',
+                'tool_version':
+                    'git_revision:e9b1fe29fe21a1cd36428c43ea2aba244bd31280',
+            },
+        )
+}
diff --git a/infra/recipe_modules/macos_sdk/api.py b/infra/recipe_modules/macos_sdk/api.py
new file mode 100644 (file)
index 0000000..cde4a8b
--- /dev/null
@@ -0,0 +1,98 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""The `macos_sdk` module provides safe functions to access a semi-hermetic
+XCode installation.
+
+Available only to Google-run bots."""
+
+from contextlib import contextmanager
+
+from recipe_engine import recipe_api
+
+
+class MacOSSDKApi(recipe_api.RecipeApi):
+  """API for using OS X SDK distributed via CIPD."""
+
+  def __init__(self, sdk_properties, *args, **kwargs):
+    super(MacOSSDKApi, self).__init__(*args, **kwargs)
+
+    self._sdk_dir = None
+    self._sdk_version = sdk_properties['sdk_version'].lower()
+    self._tool_package = sdk_properties['tool_package']
+    self._tool_version = sdk_properties['tool_version']
+
+  @property
+  def sdk_dir(self):
+    assert self._sdk_dir
+    return self._sdk_dir
+
+  @contextmanager
+  def __call__(self):
+    """Sets up the XCode SDK environment.
+
+    This call is a no-op on non-Mac platforms.
+
+    This will deploy the helper tool and the XCode.app bundle at
+    `[START_DIR]/cache/macos_sdk`.
+
+    To avoid machines rebuilding these on every run, set up a named cache in
+    your cr-buildbucket.cfg file like:
+
+        caches: {
+          # Cache for mac_toolchain tool and XCode.app
+          name: "macos_sdk"
+          path: "macos_sdk"
+        }
+
+    If you have builders which e.g. use a non-current SDK, you can give them
+    a uniqely named cache:
+
+        caches: {
+          # Cache for N-1 version mac_toolchain tool and XCode.app
+          name: "macos_sdk_old"
+          path: "macos_sdk"
+        }
+
+    Usage:
+      with api.macos_sdk():
+        # sdk with mac build bits
+
+    Raises:
+        StepFailure or InfraFailure.
+    """
+    if not self.m.platform.is_mac:
+      yield
+      return
+
+    try:
+      with self.m.context(infra_steps=True):
+        self._sdk_dir = self._ensure_sdk()
+        self.m.step('select XCode',
+                    ['sudo', 'xcode-select', '--switch', self._sdk_dir])
+      yield
+    finally:
+      with self.m.context(infra_steps=True):
+        self.m.step('reset XCode', ['sudo', 'xcode-select', '--reset'])
+
+  def _ensure_sdk(self):
+    """Ensures the mac_toolchain tool and MacOS SDK packages are installed.
+
+    Returns Path to the installed sdk app bundle."""
+    cache_dir = self.m.path['cache'].join('macos_sdk')
+    pkgs = self.m.cipd.EnsureFile()
+    pkgs.add_package(self._tool_package, self._tool_version)
+    self.m.cipd.ensure(cache_dir, pkgs)
+
+    sdk_dir = cache_dir.join('XCode.app')
+    self.m.step('install xcode', [
+        cache_dir.join('mac_toolchain'),
+        'install',
+        '-kind',
+        'mac',
+        '-xcode-version',
+        self._sdk_version,
+        '-output-dir',
+        sdk_dir,
+    ])
+    return sdk_dir
diff --git a/infra/recipe_modules/macos_sdk/examples/full.expected/linux.json b/infra/recipe_modules/macos_sdk/examples/full.expected/linux.json
new file mode 100644 (file)
index 0000000..40db66c
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "name": "ninja"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/macos_sdk/examples/full.expected/mac.json b/infra/recipe_modules/macos_sdk/examples/full.expected/mac.json
new file mode 100644 (file)
index 0000000..1fb8aba
--- /dev/null
@@ -0,0 +1,82 @@
+[
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/macos_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} git_revision:e9b1fe29fe21a1cd36428c43ea2aba244bd31280",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/macos_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "12b5025f",
+      "-output-dir",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "install xcode"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode"
+  },
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "name": "ninja"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/macos_sdk/examples/full.expected/win.json b/infra/recipe_modules/macos_sdk/examples/full.expected/win.json
new file mode 100644 (file)
index 0000000..40db66c
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "name": "ninja"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/macos_sdk/examples/full.py b/infra/recipe_modules/macos_sdk/examples/full.py
new file mode 100644 (file)
index 0000000..a3348ab
--- /dev/null
@@ -0,0 +1,23 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'macos_sdk',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+
+def RunSteps(api):
+  with api.macos_sdk():
+    sdk_dir = api.macos_sdk.sdk_dir if api.platform.is_mac else None
+    api.step('gn', ['gn', 'gen', 'out/Release'])
+    api.step('ninja', ['ninja', '-C', 'out/Release'])
+
+
+def GenTests(api):
+  for platform in ('linux', 'mac', 'win'):
+    yield (api.test(platform) + api.platform.name(platform) +
+           api.properties.generic(buildername='test_builder'))
diff --git a/infra/recipe_modules/target/__init__.py b/infra/recipe_modules/target/__init__.py
new file mode 100644 (file)
index 0000000..1239fd2
--- /dev/null
@@ -0,0 +1,7 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'recipe_engine/platform',
+]
diff --git a/infra/recipe_modules/target/api.py b/infra/recipe_modules/target/api.py
new file mode 100644 (file)
index 0000000..850c104
--- /dev/null
@@ -0,0 +1,98 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from recipe_engine import recipe_api
+
+
+PLATFORM_TO_TRIPLE = {
+  'fuchsia-amd64': 'x86_64-fuchsia',
+  'fuchsia-arm64': 'aarch64-fuchsia',
+  'linux-amd64': 'x86_64-linux-gnu',
+  'linux-arm64': 'aarch64-linux-gnu',
+  'mac-amd64': 'x86_64-apple-darwin',
+  'mac-arm64': 'arm64-apple-darwin',
+}
+PLATFORMS = PLATFORM_TO_TRIPLE.keys()
+
+
+class Target(object):
+
+  def __init__(self, api, os, arch):
+    self.m = api
+    self._os = os
+    self._arch = arch
+
+  @property
+  def is_win(self):
+    """Returns True iff the target platform is Windows."""
+    return self.os == 'windows'
+
+  @property
+  def is_mac(self):
+    """Returns True iff the target platform is macOS."""
+    return self.os == 'mac'
+
+  @property
+  def is_linux(self):
+    """Returns True iff the target platform is Linux."""
+    return self.os == 'linux'
+
+  @property
+  def is_host(self):
+    """Returns True iff the target platform is host."""
+    return self == self.m.host
+
+  @property
+  def os(self):
+    """Returns the target os name which will be in:
+      * windows
+      * mac
+      * linux
+    """
+    return self._os
+
+  @property
+  def arch(self):
+    """Returns the current CPU architecture."""
+    return self._arch
+
+  @property
+  def platform(self):
+    """Returns the target platform in the <os>-<arch> format."""
+    return '%s-%s' % (self.os, self.arch)
+
+  @property
+  def triple(self):
+    """Returns the target triple."""
+    return PLATFORM_TO_TRIPLE[self.platform]
+
+  def __str__(self):
+    return self.platform
+
+  def __eq__(self, other):
+    if isinstance(other, Target):
+      return self._os == other._os and self._arch == other._arch
+    return False
+
+  def __ne__(self, other):
+    return not self.__eq__(other)
+
+
+class TargetApi(recipe_api.RecipeApi):
+
+  def __call__(self, platform):
+    return Target(self, *platform.split('-', 2))
+
+  @property
+  def host(self):
+    return Target(self, self.m.platform.name.replace('win', 'windows'), {
+        'intel': {
+            32: '386',
+            64: 'amd64',
+        },
+        'arm': {
+            32: 'armv6',
+            64: 'arm64',
+        },
+    }[self.m.platform.arch][self.m.platform.bits])
diff --git a/infra/recipe_modules/target/examples/full.expected/linux.json b/infra/recipe_modules/target/examples/full.expected/linux.json
new file mode 100644 (file)
index 0000000..947f5d2
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.expected/mac.json b/infra/recipe_modules/target/examples/full.expected/mac.json
new file mode 100644 (file)
index 0000000..947f5d2
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.expected/win.json b/infra/recipe_modules/target/examples/full.expected/win.json
new file mode 100644 (file)
index 0000000..947f5d2
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.py b/infra/recipe_modules/target/examples/full.py
new file mode 100644 (file)
index 0000000..c47c86a
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'target',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+
+def RunSteps(api):
+  target = api.target('fuchsia-arm64')
+  assert not target.is_win
+  assert not target.is_linux
+  assert not target.is_mac
+  assert api.target.host.is_host
+  assert target != api.target.host
+  assert target != 'foo'
+  step_result = api.step('platform things', cmd=None)
+  step_result.presentation.logs['name'] = [target.os]
+  step_result.presentation.logs['arch'] = [target.arch]
+  step_result.presentation.logs['platform'] = [target.platform]
+  step_result.presentation.logs['triple'] = [target.triple]
+  step_result.presentation.logs['string'] = [str(target)]
+
+
+def GenTests(api):
+  for platform in ('linux', 'mac', 'win'):
+    yield api.test(platform) + api.platform.name(platform)
diff --git a/infra/recipe_modules/windows_sdk/__init__.py b/infra/recipe_modules/windows_sdk/__init__.py
new file mode 100644 (file)
index 0000000..83323a8
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'recipe_engine/cipd',
+    'recipe_engine/context',
+    'recipe_engine/json',
+    'recipe_engine/path',
+    'recipe_engine/platform',
+    'recipe_engine/step',
+]
+
+from recipe_engine.recipe_api import Property
+from recipe_engine.config import ConfigGroup, Single
+
+PROPERTIES = {
+    '$gn/windows_sdk':
+        Property(
+            help='Properties specifically for the windows_sdk module.',
+            param_name='sdk_properties',
+            kind=ConfigGroup(
+                # The CIPD package and version.
+                sdk_package=Single(str),
+                sdk_version=Single(str)),
+            default={
+                'sdk_package': 'chrome_internal/third_party/sdk/windows',
+                'sdk_version': 'uploaded:2019-09-06'
+            },
+        )
+}
diff --git a/infra/recipe_modules/windows_sdk/api.py b/infra/recipe_modules/windows_sdk/api.py
new file mode 100644 (file)
index 0000000..d47f984
--- /dev/null
@@ -0,0 +1,108 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from contextlib import contextmanager
+
+from recipe_engine import recipe_api
+
+
+class WindowsSDKApi(recipe_api.RecipeApi):
+  """API for using Windows SDK distributed via CIPD."""
+
+  def __init__(self, sdk_properties, *args, **kwargs):
+    super(WindowsSDKApi, self).__init__(*args, **kwargs)
+
+    self._sdk_package = sdk_properties['sdk_package']
+    self._sdk_version = sdk_properties['sdk_version']
+
+  @contextmanager
+  def __call__(self):
+    """Setups the Windows SDK environment.
+
+    This call is a no-op on non-Windows platforms.
+
+    Raises:
+        StepFailure or InfraFailure.
+    """
+    if not self.m.platform.is_win:
+      yield
+      return
+
+    try:
+      with self.m.context(infra_steps=True):
+        sdk_dir = self._ensure_sdk()
+      with self.m.context(**self._sdk_env(sdk_dir)):
+        yield
+    finally:
+      # cl.exe automatically starts background mspdbsrv.exe daemon which
+      # needs to be manually stopped so Swarming can tidy up after itself.
+      self.m.step('taskkill mspdbsrv',
+                  ['taskkill.exe', '/f', '/t', '/im', 'mspdbsrv.exe'])
+
+  def _ensure_sdk(self):
+    """Ensures the Windows SDK CIPD package is installed.
+
+    Returns the directory where the SDK package has been installed.
+
+    Args:
+      path (path): Path to a directory.
+      version (str): CIPD instance ID, tag or ref.
+    """
+    sdk_dir = self.m.path['cache'].join('windows_sdk')
+    pkgs = self.m.cipd.EnsureFile()
+    pkgs.add_package(self._sdk_package, self._sdk_version)
+    self.m.cipd.ensure(sdk_dir, pkgs)
+    return sdk_dir
+
+  def _sdk_env(self, sdk_dir):
+    """Constructs the environment for the SDK.
+
+    Returns environment and environment prefixes.
+
+    Args:
+      sdk_dir (path): Path to a directory containing the SDK.
+    """
+    env = {}
+    env_prefixes = {}
+
+    # Load .../win_sdk/bin/SetEnv.${arch}.json to extract the required
+    # environment. It contains a dict that looks like this:
+    # {
+    #   "env": {
+    #     "VAR": [["..", "..", "x"], ["..", "..", "y"]],
+    #     ...
+    #   }
+    # }
+    # All these environment variables need to be added to the environment
+    # for the compiler and linker to work.
+    filename = 'SetEnv.%s.json' % {32: 'x86', 64: 'x64'}[self.m.platform.bits]
+    step_result = self.m.json.read(
+        'read %s' % filename,
+        sdk_dir.join('win_sdk', 'bin', filename),
+        step_test_data=lambda: self.m.json.test_api.output({
+            'env': {
+                'PATH': [['..', '..', 'win_sdk', 'bin', 'x64']],
+                'VSINSTALLDIR': [['..', '..\\']],},}))
+    data = step_result.json.output.get('env')
+    for key in data:
+      # recipes' Path() does not like .., ., \, or /, so this is cumbersome.
+      # What we want to do is:
+      #   [sdk_bin_dir.join(*e) for e in env[k]]
+      # Instead do that badly, and rely (but verify) on the fact that the paths
+      # are all specified relative to the root, but specified relative to
+      # win_sdk/bin (i.e. everything starts with "../../".)
+      results = []
+      for value in data[key]:
+        assert value[0] == '..' and (value[1] == '..' or value[1] == '..\\')
+        results.append('%s' % sdk_dir.join(*value[2:]))
+
+      # PATH is special-cased because we don't want to overwrite other things
+      # like C:\Windows\System32. Others are replacements because prepending
+      # doesn't necessarily makes sense, like VSINSTALLDIR.
+      if key.lower() == 'path':
+        env_prefixes[key] = results
+      else:
+        env[key] = ';'.join(results)
+
+    return {'env': env, 'env_prefixes': env_prefixes}
diff --git a/infra/recipe_modules/windows_sdk/examples/full.expected/linux.json b/infra/recipe_modules/windows_sdk/examples/full.expected/linux.json
new file mode 100644 (file)
index 0000000..40db66c
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "name": "ninja"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/windows_sdk/examples/full.expected/mac.json b/infra/recipe_modules/windows_sdk/examples/full.expected/mac.json
new file mode 100644 (file)
index 0000000..40db66c
--- /dev/null
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "name": "ninja"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/windows_sdk/examples/full.expected/win.json b/infra/recipe_modules/windows_sdk/examples/full.expected/win.json
new file mode 100644 (file)
index 0000000..f8b2b71
--- /dev/null
@@ -0,0 +1,107 @@
+[
+  {
+    "cmd": [
+      "cipd.bat",
+      "ensure",
+      "-root",
+      "[CACHE]\\windows_sdk",
+      "-ensure-file",
+      "chrome_internal/third_party/sdk/windows uploaded:2019-09-06",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"chrome_internal/third_party/sdk/windows\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[CACHE]\\windows_sdk\\win_sdk\\bin\\SetEnv.x64.json",
+      "/path/to/tmp/json"
+    ],
+    "name": "read SetEnv.x64.json",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"env\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"PATH\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"bin\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"x64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"VSINSTALLDIR\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\\\\\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "gn",
+      "gen",
+      "out/Release"
+    ],
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "gn"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "out/Release"
+    ],
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "ninja"
+  },
+  {
+    "cmd": [
+      "taskkill.exe",
+      "/f",
+      "/t",
+      "/im",
+      "mspdbsrv.exe"
+    ],
+    "name": "taskkill mspdbsrv"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/windows_sdk/examples/full.py b/infra/recipe_modules/windows_sdk/examples/full.py
new file mode 100644 (file)
index 0000000..902c491
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'windows_sdk',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+
+def RunSteps(api):
+  with api.windows_sdk():
+    api.step('gn', ['gn', 'gen', 'out/Release'])
+    api.step('ninja', ['ninja', '-C', 'out/Release'])
+
+
+def GenTests(api):
+  for platform in ('linux', 'mac', 'win'):
+    properties = {
+        'buildername': 'test_builder',
+    }
+    yield (api.test(platform) + api.platform.name(platform) +
+           api.properties.generic(**properties))
diff --git a/infra/recipes.py b/infra/recipes.py
new file mode 100755 (executable)
index 0000000..2fe0086
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+
+# Copyright 2017 The LUCI Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+
+"""Bootstrap script to clone and forward to the recipe engine tool.
+
+*******************
+** DO NOT MODIFY **
+*******************
+
+This is a copy of https://chromium.googlesource.com/infra/luci/recipes-py/+/master/recipes.py.
+To fix bugs, fix in the googlesource repo then run the autoroller.
+"""
+
+import argparse
+import json
+import logging
+import os
+import random
+import subprocess
+import sys
+import time
+import urlparse
+
+from collections import namedtuple
+
+from cStringIO import StringIO
+
+# The dependency entry for the recipe_engine in the client repo's recipes.cfg
+#
+# url (str) - the url to the engine repo we want to use.
+# revision (str) - the git revision for the engine to get.
+# branch (str) - the branch to fetch for the engine as an absolute ref (e.g.
+#   refs/heads/master)
+EngineDep = namedtuple('EngineDep',
+                       'url revision branch')
+
+
+class MalformedRecipesCfg(Exception):
+  def __init__(self, msg, path):
+    super(MalformedRecipesCfg, self).__init__('malformed recipes.cfg: %s: %r'
+                                              % (msg, path))
+
+
+def parse(repo_root, recipes_cfg_path):
+  """Parse is a lightweight a recipes.cfg file parser.
+
+  Args:
+    repo_root (str) - native path to the root of the repo we're trying to run
+      recipes for.
+    recipes_cfg_path (str) - native path to the recipes.cfg file to process.
+
+  Returns (as tuple):
+    engine_dep (EngineDep|None): The recipe_engine dependency, or None, if the
+      current repo IS the recipe_engine.
+    recipes_path (str) - native path to where the recipes live inside of the
+      current repo (i.e. the folder containing `recipes/` and/or
+      `recipe_modules`)
+  """
+  with open(recipes_cfg_path, 'rU') as fh:
+    pb = json.load(fh)
+
+  try:
+    if pb['api_version'] != 2:
+      raise MalformedRecipesCfg('unknown version %d' % pb['api_version'],
+                                recipes_cfg_path)
+
+    # If we're running ./recipes.py from the recipe_engine repo itself, then
+    # return None to signal that there's no EngineDep.
+    repo_name = pb.get('repo_name')
+    if not repo_name:
+      repo_name = pb['project_id']
+    if repo_name == 'recipe_engine':
+      return None, pb.get('recipes_path', '')
+
+    engine = pb['deps']['recipe_engine']
+
+    if 'url' not in engine:
+      raise MalformedRecipesCfg(
+        'Required field "url" in dependency "recipe_engine" not found',
+        recipes_cfg_path)
+
+    engine.setdefault('revision', '')
+    engine.setdefault('branch', 'refs/heads/master')
+    recipes_path = pb.get('recipes_path', '')
+
+    # TODO(iannucci): only support absolute refs
+    if not engine['branch'].startswith('refs/'):
+      engine['branch'] = 'refs/heads/' + engine['branch']
+
+    recipes_path = os.path.join(
+      repo_root, recipes_path.replace('/', os.path.sep))
+    return EngineDep(**engine), recipes_path
+  except KeyError as ex:
+    raise MalformedRecipesCfg(ex.message, recipes_cfg_path)
+
+
+_BAT = '.bat' if sys.platform.startswith(('win', 'cygwin')) else ''
+GIT = 'git' + _BAT
+VPYTHON = 'vpython' + _BAT
+CIPD = 'cipd' + _BAT
+REQUIRED_BINARIES = {GIT, VPYTHON, CIPD}
+
+
+def _is_executable(path):
+  return os.path.isfile(path) and os.access(path, os.X_OK)
+
+# TODO: Use shutil.which once we switch to Python3.
+def _is_on_path(basename):
+  for path in os.environ['PATH'].split(os.pathsep):
+    full_path = os.path.join(path, basename)
+    if _is_executable(full_path):
+      return True
+  return False
+
+
+def _subprocess_call(argv, **kwargs):
+  logging.info('Running %r', argv)
+  return subprocess.call(argv, **kwargs)
+
+
+def _git_check_call(argv, **kwargs):
+  argv = [GIT]+argv
+  logging.info('Running %r', argv)
+  subprocess.check_call(argv, **kwargs)
+
+
+def _git_output(argv, **kwargs):
+  argv = [GIT]+argv
+  logging.info('Running %r', argv)
+  return subprocess.check_output(argv, **kwargs)
+
+
+def parse_args(argv):
+  """This extracts a subset of the arguments that this bootstrap script cares
+  about. Currently this consists of:
+    * an override for the recipe engine in the form of `-O recipe_engine=/path`
+    * the --package option.
+  """
+  PREFIX = 'recipe_engine='
+
+  p = argparse.ArgumentParser(add_help=False)
+  p.add_argument('-O', '--project-override', action='append')
+  p.add_argument('--package', type=os.path.abspath)
+  args, _ = p.parse_known_args(argv)
+  for override in args.project_override or ():
+    if override.startswith(PREFIX):
+      return override[len(PREFIX):], args.package
+  return None, args.package
+
+
+def checkout_engine(engine_path, repo_root, recipes_cfg_path):
+  dep, recipes_path = parse(repo_root, recipes_cfg_path)
+  if dep is None:
+    # we're running from the engine repo already!
+    return os.path.join(repo_root, recipes_path)
+
+  url = dep.url
+
+  if not engine_path and url.startswith('file://'):
+    engine_path = urlparse.urlparse(url).path
+
+  if not engine_path:
+    revision = dep.revision
+    branch = dep.branch
+
+    # Ensure that we have the recipe engine cloned.
+    engine_path = os.path.join(recipes_path, '.recipe_deps', 'recipe_engine')
+
+    with open(os.devnull, 'w') as NUL:
+      # Note: this logic mirrors the logic in recipe_engine/fetch.py
+      _git_check_call(['init', engine_path], stdout=NUL)
+
+      try:
+        _git_check_call(['rev-parse', '--verify', '%s^{commit}' % revision],
+                        cwd=engine_path, stdout=NUL, stderr=NUL)
+      except subprocess.CalledProcessError:
+        _git_check_call(['fetch', url, branch], cwd=engine_path, stdout=NUL,
+                        stderr=NUL)
+
+    try:
+      _git_check_call(['diff', '--quiet', revision], cwd=engine_path)
+    except subprocess.CalledProcessError:
+      _git_check_call(['reset', '-q', '--hard', revision], cwd=engine_path)
+
+    # If the engine has refactored/moved modules we need to clean all .pyc files
+    # or things will get squirrely.
+    _git_check_call(['clean', '-qxf'], cwd=engine_path)
+
+  return engine_path
+
+
+def main():
+  for required_binary in REQUIRED_BINARIES:
+    if not _is_on_path(required_binary):
+      return 'Required binary is not found on PATH: %s' % required_binary
+
+  if '--verbose' in sys.argv:
+    logging.getLogger().setLevel(logging.INFO)
+
+  args = sys.argv[1:]
+  engine_override, recipes_cfg_path = parse_args(args)
+
+  if recipes_cfg_path:
+    # calculate repo_root from recipes_cfg_path
+    repo_root = os.path.dirname(
+      os.path.dirname(
+        os.path.dirname(recipes_cfg_path)))
+  else:
+    # find repo_root with git and calculate recipes_cfg_path
+    repo_root = (_git_output(
+      ['rev-parse', '--show-toplevel'],
+      cwd=os.path.abspath(os.path.dirname(__file__))).strip())
+    repo_root = os.path.abspath(repo_root)
+    recipes_cfg_path = os.path.join(repo_root, 'infra', 'config', 'recipes.cfg')
+    args = ['--package', recipes_cfg_path] + args
+
+  engine_path = checkout_engine(engine_override, repo_root, recipes_cfg_path)
+
+  return _subprocess_call([
+      VPYTHON, '-u',
+      os.path.join(engine_path, 'recipe_engine', 'main.py')] + args)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/infra/recipes/gn.expected/ci_linux.json b/infra/recipes/gn.expected/ci_linux.json
new file mode 100644 (file)
index 0000000..61b1fa7
--- /dev/null
@@ -0,0 +1,506 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration\n@Subdir sysroot\nfuchsia/third_party/sysroot/linux git_revision:c912d089c3d46d8982fdef76a50514cca79b6132",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"sysroot\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:c91\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/sysroot/linux\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/rpmalloc"
+    ],
+    "name": "rpmalloc.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc",
+      "6bb6ca97a8d6a72d626153fd8431ef8477a21145"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "x86-64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "arm64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-amd64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.build gn/gn/linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-arm64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.upload.build gn/gn/linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/ci_mac.json b/infra/recipes/gn.expected/ci_mac.json
new file mode 100644 (file)
index 0000000..5816d9c
--- /dev/null
@@ -0,0 +1,513 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/macos_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} git_revision:e9b1fe29fe21a1cd36428c43ea2aba244bd31280",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/macos_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "12b5025f",
+      "-output-dir",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "install xcode"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode"
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "debug.xcrun sdk-path",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "debug.xcrun toolchain",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug.mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "release.xcrun sdk-path",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "release.xcrun toolchain",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-amd64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/mac-amd64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.upload.build gn/gn/mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/mac-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "release.xcrun sdk-path (2)",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "release.xcrun toolchain (2)",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-arm64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/mac-arm64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-arm64.upload.build gn/gn/mac-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/mac-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/ci_win.json b/infra/recipes/gn.expected/ci_win.json
new file mode 100644 (file)
index 0000000..842f015
--- /dev/null
@@ -0,0 +1,342 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]\\gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd.bat",
+      "ensure",
+      "-root",
+      "[START_DIR]\\cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd.bat",
+      "ensure",
+      "-root",
+      "[CACHE]\\windows_sdk",
+      "-ensure-file",
+      "chrome_internal/third_party/sdk/windows uploaded:2019-09-06",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"chrome_internal/third_party/sdk/windows\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[CACHE]\\windows_sdk\\win_sdk\\bin\\SetEnv.x64.json",
+      "/path/to/tmp/json"
+    ],
+    "name": "read SetEnv.x64.json",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"env\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"PATH\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"bin\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"x64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"VSINSTALLDIR\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\\\\\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]\\gn\\build\\gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\cipd\\ninja",
+      "-C",
+      "[START_DIR]\\gn\\out"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\gn\\out\\gn_unittests"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]\\gn\\build\\gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\cipd\\ninja",
+      "-C",
+      "[START_DIR]\\gn\\out"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\gn\\out\\gn_unittests"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.windows-amd64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd.bat",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn.exe\"}, {\"version_file\": \".versions/gn.exe.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/windows-amd64\", \"root\": \"[START_DIR]\\\\gn\\\\out\"}",
+      "-out",
+      "[CLEANUP]\\gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.upload.build gn/gn/windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/windows-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "taskkill.exe",
+      "/f",
+      "/t",
+      "/im",
+      "mspdbsrv.exe"
+    ],
+    "name": "taskkill mspdbsrv"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/cipd_exists.json b/infra/recipes/gn.expected/cipd_exists.json
new file mode 100644 (file)
index 0000000..e7d6a03
--- /dev/null
@@ -0,0 +1,584 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration\n@Subdir sysroot\nfuchsia/third_party/sysroot/linux git_revision:c912d089c3d46d8982fdef76a50514cca79b6132",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"sysroot\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:c91\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/sysroot/linux\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/rpmalloc"
+    ],
+    "name": "rpmalloc.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc",
+      "6bb6ca97a8d6a72d626153fd8431ef8477a21145"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "x86-64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "arm64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-amd64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.build gn/gn/linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "search",
+      "gn/gn/linux-amd64",
+      "-tag",
+      "git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.cipd search gn/gn/linux-amd64 git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@    {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"instance_id\": \"resolved-instance_id-of-git_revision:aaa\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"package\": \"gn/gn/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  ]@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64.upload.Package is up-to-date",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-arm64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.upload.build gn/gn/linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "search",
+      "gn/gn/linux-arm64",
+      "-tag",
+      "git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.upload.cipd search gn/gn/linux-arm64 git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@    {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"package\": \"gn/gn/linux-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  ]@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64.upload.Package is up-to-date",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/cipd_register.json b/infra/recipes/gn.expected/cipd_register.json
new file mode 100644 (file)
index 0000000..6eebe17
--- /dev/null
@@ -0,0 +1,606 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration\n@Subdir sysroot\nfuchsia/third_party/sysroot/linux git_revision:c912d089c3d46d8982fdef76a50514cca79b6132",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"sysroot\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:c91\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/sysroot/linux\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/rpmalloc"
+    ],
+    "name": "rpmalloc.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc",
+      "6bb6ca97a8d6a72d626153fd8431ef8477a21145"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "x86-64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "arm64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-amd64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.build gn/gn/linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "search",
+      "gn/gn/linux-amd64",
+      "-tag",
+      "git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.cipd search gn/gn/linux-amd64 git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": []@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-register",
+      "[CLEANUP]/gn.cipd",
+      "-ref",
+      "latest",
+      "-tag",
+      "git_repository:https://gn.googlesource.com/gn",
+      "-tag",
+      "git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.upload.register gn/gn/linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64.upload",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-pkg-def",
+      "{\"data\": [{\"file\": \"gn\"}, {\"version_file\": \".versions/gn.cipd_version\"}], \"install_mode\": \"copy\", \"package\": \"gn/gn/linux-arm64\", \"root\": \"[START_DIR]/gn/out\"}",
+      "-out",
+      "[CLEANUP]/gn.cipd",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.upload.build gn/gn/linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"gn/gn/linux-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "search",
+      "gn/gn/linux-arm64",
+      "-tag",
+      "git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.upload.cipd search gn/gn/linux-arm64 git_revision:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@    {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"package\": \"gn/gn/linux-arm64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  ]@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64.upload.Package is up-to-date",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/cq_linux.json b/infra/recipes/gn.expected/cq_linux.json
new file mode 100644 (file)
index 0000000..7ac4cfc
--- /dev/null
@@ -0,0 +1,453 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      ""
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://gn.googlesource.com/gn",
+      "refs/changes/56/123456/7"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration\n@Subdir sysroot\nfuchsia/third_party/sysroot/linux git_revision:c912d089c3d46d8982fdef76a50514cca79b6132",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"sysroot\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:c91\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/sysroot/linux\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/rpmalloc"
+    ],
+    "name": "rpmalloc.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc",
+      "6bb6ca97a8d6a72d626153fd8431ef8477a21145"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "infra_step": true,
+    "name": "rpmalloc.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "x86-64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-amd64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "rpmalloc.build rpmalloc-linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/rpmalloc/configure.py",
+      "-c",
+      "release",
+      "-a",
+      "arm64",
+      "--lto"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.configure",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/rpmalloc",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "rpmalloc.build rpmalloc-linux-arm64.ninja",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "debug.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/x86-64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.linux-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf",
+      "--link-lib=[START_DIR]/rpmalloc/lib/linux/release/arm64/librpmallocwrap.a"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=aarch64-linux-gnu --sysroot=[START_DIR]/cipd/sysroot -static-libstdc++"
+    },
+    "name": "release.linux-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/cq_mac.json b/infra/recipes/gn.expected/cq_mac.json
new file mode 100644 (file)
index 0000000..3965438
--- /dev/null
@@ -0,0 +1,460 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]/gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      ""
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://gn.googlesource.com/gn",
+      "refs/changes/56/123456/7"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.fetch 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "infra_step": true,
+    "name": "git.checkout 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2\nfuchsia/third_party/clang/${platform} integration",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-integration-----\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"fuchsia/third_party/clang/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/macos_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} git_revision:e9b1fe29fe21a1cd36428c43ea2aba244bd31280",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/macos_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "12b5025f",
+      "-output-dir",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "install xcode"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "[CACHE]/macos_sdk/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode"
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "debug.xcrun sdk-path",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "debug.xcrun toolchain",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug.mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "debug.mac-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "release.xcrun sdk-path",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "release.xcrun toolchain",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/gn/out/gn_unittests"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=x86_64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--show-sdk-path"
+    ],
+    "name": "release.xcrun sdk-path (2)",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[sdk-path]@/some/xcode/path@@@",
+      "@@@STEP_LOG_END@raw_io.output[sdk-path]@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "--toolchain",
+      "clang",
+      "clang++",
+      "-xc++",
+      "-fsyntax-only",
+      "-Wp,-v",
+      "-"
+    ],
+    "name": "release.xcrun toolchain (2)",
+    "stderr": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output[toolchain]@[CACHE]/macos_sdk/XCode.app/include/c++/v1@@@",
+      "@@@STEP_LOG_END@raw_io.output[toolchain]@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release.mac-arm64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/gn/build/gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-arm64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd/ninja",
+      "-C",
+      "[START_DIR]/gn/out"
+    ],
+    "cwd": "[START_DIR]/gn",
+    "env": {
+      "AR": "[START_DIR]/cipd/bin/llvm-ar",
+      "CC": "[START_DIR]/cipd/bin/clang",
+      "CFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path -nostdinc++ -cxx-isystem [CACHE]/macos_sdk/XCode.app/include/c++/v1",
+      "CXX": "[START_DIR]/cipd/bin/clang++",
+      "LDFLAGS": "--target=arm64-apple-darwin --sysroot=/some/xcode/path"
+    },
+    "name": "release.mac-arm64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.expected/cq_win.json b/infra/recipes/gn.expected/cq_win.json
new file mode 100644 (file)
index 0000000..7e4424b
--- /dev/null
@@ -0,0 +1,328 @@
+[
+  {
+    "cmd": [],
+    "name": "git"
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "[START_DIR]\\gn"
+    ],
+    "infra_step": true,
+    "name": "git.init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "https://gn.googlesource.com/gn",
+      ""
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.rev-parse",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://gn.googlesource.com/gn",
+      "refs/changes/56/123456/7"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.fetch 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "infra_step": true,
+    "name": "git.checkout 123456/7",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd.bat",
+      "ensure",
+      "-root",
+      "[START_DIR]\\cipd",
+      "-ensure-file",
+      "infra/ninja/${platform} version:1.8.2",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:1.8.2---\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/ninja/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd.bat",
+      "ensure",
+      "-root",
+      "[CACHE]\\windows_sdk",
+      "-ensure-file",
+      "chrome_internal/third_party/sdk/windows uploaded:2019-09-06",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"chrome_internal/third_party/sdk/windows\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[CACHE]\\windows_sdk\\win_sdk\\bin\\SetEnv.x64.json",
+      "/path/to/tmp/json"
+    ],
+    "name": "read SetEnv.x64.json",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"env\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"PATH\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"bin\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"x64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"VSINSTALLDIR\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      [@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\\\\\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      ]@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "debug"
+  },
+  {
+    "cmd": [],
+    "name": "debug.windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]\\gn\\build\\gen.py",
+      "-d"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\cipd\\ninja",
+      "-C",
+      "[START_DIR]\\gn\\out"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\gn\\out\\gn_unittests"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "debug.windows-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "release"
+  },
+  {
+    "cmd": [],
+    "name": "release.windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]\\gn\\build\\gen.py",
+      "--use-lto",
+      "--use-icf"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.generate",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\cipd\\ninja",
+      "-C",
+      "[START_DIR]\\gn\\out"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.build",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\gn\\out\\gn_unittests"
+    ],
+    "cwd": "[START_DIR]\\gn",
+    "env": {
+      "VSINSTALLDIR": "[CACHE]\\windows_sdk"
+    },
+    "env_prefixes": {
+      "PATH": [
+        "[CACHE]\\windows_sdk\\win_sdk\\bin\\x64"
+      ]
+    },
+    "name": "release.windows-amd64.test",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "taskkill.exe",
+      "/f",
+      "/t",
+      "/im",
+      "mspdbsrv.exe"
+    ],
+    "name": "taskkill mspdbsrv"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipes/gn.py b/infra/recipes/gn.py
new file mode 100644 (file)
index 0000000..0cb4269
--- /dev/null
@@ -0,0 +1,324 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed under the Apache License, Version 2.0
+# that can be found in the LICENSE file.
+"""Recipe for building GN."""
+
+from recipe_engine.recipe_api import Property
+
+DEPS = [
+    'recipe_engine/buildbucket',
+    'recipe_engine/cipd',
+    'recipe_engine/context',
+    'recipe_engine/file',
+    'recipe_engine/json',
+    'recipe_engine/path',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/python',
+    'recipe_engine/raw_io',
+    'recipe_engine/step',
+    'target',
+    'macos_sdk',
+    'windows_sdk',
+]
+
+PROPERTIES = {
+    'repository': Property(kind=str, default='https://gn.googlesource.com/gn'),
+}
+
+# On select platforms, link the GN executable against rpmalloc for a small 10% speed boost.
+RPMALLOC_GIT_URL = 'https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc'
+RPMALLOC_REVISION = '6bb6ca97a8d6a72d626153fd8431ef8477a21145'
+
+# Used to convert os and arch strings to rpmalloc format
+RPMALLOC_MAP = {
+    'amd64': 'x86-64',
+    'mac': 'macos',
+}
+
+
+def _get_libcxx_include_path(api):
+  # Run the preprocessor with an empty input and print all include paths.
+  lines = api.step(
+      'xcrun toolchain', [
+          'xcrun', '--toolchain', 'clang', 'clang++', '-xc++', '-fsyntax-only',
+          '-Wp,-v', '-'
+      ],
+      stderr=api.raw_io.output(name='toolchain', add_output_log=True),
+      step_test_data=lambda: api.raw_io.test_api.stream_output(
+          str(api.macos_sdk.sdk_dir.join('include', 'c++', 'v1')),
+          stream='stderr')).stderr.splitlines()
+  # Iterate over all include paths and look for the SDK libc++ one.
+  sdk_dir = str(api.macos_sdk.sdk_dir)
+  for line in lines:
+    line = line.strip()
+    if line.startswith(sdk_dir) and 'include/c++/v1' in line:
+      return line
+  return None  # pragma: no cover
+
+
+def _get_compilation_environment(api, target, cipd_dir):
+  if target.is_linux:
+    triple = '--target=%s' % target.triple
+    sysroot = '--sysroot=%s' % cipd_dir.join('sysroot')
+    env = {
+        'CC': cipd_dir.join('bin', 'clang'),
+        'CXX': cipd_dir.join('bin', 'clang++'),
+        'AR': cipd_dir.join('bin', 'llvm-ar'),
+        'CFLAGS': '%s %s' % (triple, sysroot),
+        'LDFLAGS': '%s %s -static-libstdc++' % (triple, sysroot),
+    }
+  elif target.is_mac:
+    triple = '--target=%s' % target.triple
+    sysroot = '--sysroot=%s' % api.step(
+        'xcrun sdk-path', ['xcrun', '--show-sdk-path'],
+        stdout=api.raw_io.output(name='sdk-path', add_output_log=True),
+        step_test_data=lambda: api.raw_io.test_api.stream_output(
+            '/some/xcode/path')).stdout.strip()
+    stdlib = cipd_dir.join('lib', 'libc++.a')
+    cxx_include = _get_libcxx_include_path(api)
+    env = {
+        'CC':
+            cipd_dir.join('bin', 'clang'),
+        'CXX':
+            cipd_dir.join('bin', 'clang++'),
+        'AR':
+            cipd_dir.join('bin', 'llvm-ar'),
+        'CFLAGS':
+            '%s %s -nostdinc++ -cxx-isystem %s' %
+            (triple, sysroot, cxx_include),
+        # TODO(phosek): Use the system libc++ temporarily until we
+        # have universal libc++.a for macOS that supports both x86_64
+        # and arm64.
+        'LDFLAGS':
+            '%s %s' % (triple, sysroot),
+    }
+  else:
+    env = {}
+
+  return env
+
+
+def RunSteps(api, repository):
+  src_dir = api.path['start_dir'].join('gn')
+
+  # TODO: Verify that building and linking rpmalloc works on OS X and Windows as
+  # well.
+  with api.step.nest('git'), api.context(infra_steps=True):
+    api.step('init', ['git', 'init', src_dir])
+
+    with api.context(cwd=src_dir):
+      build_input = api.buildbucket.build_input
+      ref = (
+          build_input.gitiles_commit.id
+          if build_input.gitiles_commit else 'refs/heads/master')
+      # Fetch tags so `git describe` works.
+      api.step('fetch', ['git', 'fetch', '--tags', repository, ref])
+      api.step('checkout', ['git', 'checkout', 'FETCH_HEAD'])
+      revision = api.step(
+          'rev-parse', ['git', 'rev-parse', 'HEAD'],
+          stdout=api.raw_io.output()).stdout.strip()
+      for change in build_input.gerrit_changes:
+        api.step('fetch %s/%s' % (change.change, change.patchset), [
+            'git', 'fetch', repository,
+            'refs/changes/%s/%s/%s' %
+            (str(change.change)[-2:], change.change, change.patchset)
+        ])
+        api.step('checkout %s/%s' % (change.change, change.patchset),
+                 ['git', 'checkout', 'FETCH_HEAD'])
+
+  with api.context(infra_steps=True):
+    cipd_dir = api.path['start_dir'].join('cipd')
+    pkgs = api.cipd.EnsureFile()
+    pkgs.add_package('infra/ninja/${platform}', 'version:1.8.2')
+    if api.platform.is_linux or api.platform.is_mac:
+      pkgs.add_package('fuchsia/third_party/clang/${platform}', 'integration')
+    if api.platform.is_linux:
+      pkgs.add_package('fuchsia/third_party/sysroot/linux',
+                       'git_revision:c912d089c3d46d8982fdef76a50514cca79b6132',
+                       'sysroot')
+    api.cipd.ensure(cipd_dir, pkgs)
+
+  def release_targets():
+    if api.platform.is_linux:
+      return [api.target('linux-amd64'), api.target('linux-arm64')]
+    elif api.platform.is_mac:
+      return [api.target('mac-amd64'), api.target('mac-arm64')]
+    else:
+      return [api.target.host]
+
+  # The order is important since release build will get uploaded to CIPD.
+  configs = [
+      {
+          'name': 'debug',
+          'args': ['-d'],
+          'targets': [api.target.host],
+      },
+      {
+          'name': 'release',
+          'args': ['--use-lto', '--use-icf'],
+          'targets': release_targets(),
+          # TODO: Enable this for OS X and Windows.
+          'use_rpmalloc': api.platform.is_linux
+      },
+  ]
+
+  # True if any config uses rpmalloc.
+  use_rpmalloc = any(c.get('use_rpmalloc', False) for c in configs)
+
+  with api.macos_sdk(), api.windows_sdk():
+    # Build the rpmalloc static libraries if needed.
+    if use_rpmalloc:
+      # Maps a target.platform string to the location of the corresponding
+      # rpmalloc static library.
+      rpmalloc_static_libs = {}
+
+      # Get the list of all target platforms that are listed in `configs`
+      # above. Note that this is a list of Target instances, some of them
+      # may refer to the same platform string (e.g. linux-amd64).
+      #
+      # For each platform, a version of rpmalloc will be built if necessary,
+      # but doing this properly requires having a valid target instance to
+      # call _get_compilation_environment. So create a { platform -> Target }
+      # map to do that later.
+      all_config_platforms = {}
+      for c in configs:
+        if not c.get('use_rpmalloc', False):
+          continue
+        for t in c['targets']:
+          if t.platform not in all_config_platforms:
+            all_config_platforms[t.platform] = t
+
+      rpmalloc_src_dir = api.path['start_dir'].join('rpmalloc')
+      with api.step.nest('rpmalloc'):
+        api.step('init', ['git', 'init', rpmalloc_src_dir])
+        with api.context(cwd=rpmalloc_src_dir, infra_steps=True):
+          api.step(
+              'fetch',
+              ['git', 'fetch', '--tags', RPMALLOC_GIT_URL, RPMALLOC_REVISION])
+          api.step('checkout', ['git', 'checkout', 'FETCH_HEAD'])
+
+        for platform in all_config_platforms:
+          # Convert target architecture and os to rpmalloc format.
+          rpmalloc_os, rpmalloc_arch = platform.split('-')
+          rpmalloc_os = RPMALLOC_MAP.get(rpmalloc_os, rpmalloc_os)
+          rpmalloc_arch = RPMALLOC_MAP.get(rpmalloc_arch, rpmalloc_arch)
+
+          env = _get_compilation_environment(api,
+                                             all_config_platforms[platform],
+                                             cipd_dir)
+          with api.step.nest('build rpmalloc-' + platform), api.context(
+              env=env, cwd=rpmalloc_src_dir):
+            api.python(
+                'configure',
+                rpmalloc_src_dir.join('configure.py'),
+                args=['-c', 'release', '-a', rpmalloc_arch, '--lto'])
+
+            # NOTE: Only build the static library.
+            rpmalloc_static_lib = api.path.join('lib', rpmalloc_os, 'release',
+                                                rpmalloc_arch,
+                                                'librpmallocwrap.a')
+            api.step('ninja', [cipd_dir.join('ninja'), rpmalloc_static_lib])
+
+          rpmalloc_static_libs[platform] = rpmalloc_src_dir.join(
+              rpmalloc_static_lib)
+
+    for config in configs:
+      with api.step.nest(config['name']):
+        for target in config['targets']:
+          env = _get_compilation_environment(api, target, cipd_dir)
+          with api.step.nest(target.platform), api.context(
+              env=env, cwd=src_dir):
+            args = config['args']
+            if config.get('use_rpmalloc', False):
+              args = args[:] + [
+                  '--link-lib=%s' % rpmalloc_static_libs[target.platform]
+              ]
+
+            api.python('generate', src_dir.join('build', 'gen.py'), args=args)
+
+            # Windows requires the environment modifications when building too.
+            api.step('build',
+                     [cipd_dir.join('ninja'), '-C',
+                      src_dir.join('out')])
+
+            if target.is_host:
+              api.step('test', [src_dir.join('out', 'gn_unittests')])
+
+            if build_input.gerrit_changes:
+              continue
+
+            if config['name'] != 'release':
+              continue
+
+            with api.step.nest('upload'):
+              cipd_pkg_name = 'gn/gn/%s' % target.platform
+              gn = 'gn' + ('.exe' if target.is_win else '')
+
+              pkg_def = api.cipd.PackageDefinition(
+                  package_name=cipd_pkg_name,
+                  package_root=src_dir.join('out'),
+                  install_mode='copy')
+              pkg_def.add_file(src_dir.join('out', gn))
+              pkg_def.add_version_file('.versions/%s.cipd_version' % gn)
+
+              cipd_pkg_file = api.path['cleanup'].join('gn.cipd')
+
+              api.cipd.build_from_pkg(
+                  pkg_def=pkg_def,
+                  output_package=cipd_pkg_file,
+              )
+
+              if api.buildbucket.builder_id.project == 'infra-internal':
+                cipd_pin = api.cipd.search(cipd_pkg_name,
+                                           'git_revision:' + revision)
+                if cipd_pin:
+                  api.step('Package is up-to-date', cmd=None)
+                  continue
+
+                api.cipd.register(
+                    package_name=cipd_pkg_name,
+                    package_path=cipd_pkg_file,
+                    refs=['latest'],
+                    tags={
+                        'git_repository': repository,
+                        'git_revision': revision,
+                    },
+                )
+
+
+def GenTests(api):
+  for platform in ('linux', 'mac', 'win'):
+    yield (api.test('ci_' + platform) + api.platform.name(platform) +
+           api.buildbucket.ci_build(
+               project='gn',
+               git_repo='gn.googlesource.com/gn',
+           ))
+
+    yield (api.test('cq_' + platform) + api.platform.name(platform) +
+           api.buildbucket.try_build(
+               project='gn',
+               git_repo='gn.googlesource.com/gn',
+           ))
+
+  yield (api.test('cipd_exists') + api.buildbucket.ci_build(
+      project='infra-internal',
+      git_repo='gn.googlesource.com/gn',
+      revision='a' * 40,
+  ) + api.step_data(
+      'git.rev-parse', api.raw_io.stream_output('a' * 40)
+  ) + api.step_data(
+      'release.linux-amd64.upload.cipd search gn/gn/linux-amd64 git_revision:' +
+      'a' * 40,
+      api.cipd.example_search('gn/gn/linux-amd64',
+                              ['git_revision:' + 'a' * 40])))
+
+  yield (api.test('cipd_register') + api.buildbucket.ci_build(
+      project='infra-internal',
+      git_repo='gn.googlesource.com/gn',
+      revision='a' * 40,
+  ) + api.step_data(
+      'git.rev-parse', api.raw_io.stream_output('a' * 40)
+  ) + api.step_data(
+      'release.linux-amd64.upload.cipd search gn/gn/linux-amd64 git_revision:' +
+      'a' * 40, api.cipd.example_search('gn/gn/linux-amd64', [])))
diff --git a/misc/emacs/.gitignore b/misc/emacs/.gitignore
new file mode 100644 (file)
index 0000000..c531d98
--- /dev/null
@@ -0,0 +1 @@
+*.elc
diff --git a/misc/emacs/gn-mode.el b/misc/emacs/gn-mode.el
new file mode 100644 (file)
index 0000000..dc92c9b
--- /dev/null
@@ -0,0 +1,192 @@
+;;; gn-mode.el - A major mode for editing gn files.
+
+;; Copyright 2015 The Chromium Authors. All rights reserved.
+;; Use of this source code is governed by a BSD-style license that can be
+;; found in the LICENSE file.
+
+;; Author: Elliot Glaysher <erg@chromium.org>
+;; Created: April 03, 2015
+;; Keywords: tools, gn, ninja, chromium
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; A major mode for editing GN files. GN stands for Generate Ninja. GN is the
+;; meta build system used in Chromium. For more information on GN, see the GN
+;; manual: <https://gn.googlesource.com/gn/+/refs/heads/master/README.md>
+
+;;; To Do:
+
+;; - We syntax highlight builtin actions, but don't highlight instantiations of
+;;   templates. Should we?
+
+\f
+
+(require 'smie)
+
+(defgroup gn nil
+  "Major mode for editing Generate Ninja files."
+  :prefix "gn-"
+  :group 'languages)
+
+(defcustom gn-indent-basic 2
+  "The number of spaces to indent a new scope."
+  :group 'gn
+  :type 'integer)
+
+(defcustom gn-format-command "gn format --stdin"
+  "The command to run to format gn files in place."
+  :group 'gn
+  :type 'string)
+
+(defgroup gn-faces nil
+  "Faces used in Generate Ninja mode."
+  :group 'gn
+  :group 'faces)
+
+(defface gn-embedded-variable
+  '((t :inherit font-lock-variable-name-face))
+  "Font lock face used to highlight variable names in strings."
+  :group 'gn-faces)
+
+(defface gn-embedded-variable-boundary
+  '((t :bold t
+       :inherit gn-embedded-variable))
+  "Font lock face used to highlight the '$' that starts a
+variable name or the '{{' and '}}' which surround it."
+  :group 'gn-faces)
+
+(defvar gn-font-lock-reserved-keywords
+  '("true" "false" "if" "else"))
+
+(defvar gn-font-lock-target-declaration-keywords
+  '("action" "action_foreach" "bundle_data" "copy" "create_bundle" "executable"
+    "group" "loadable_module" "shared_library" "source_set" "static_library"
+    "generated_file" "target" "rust_library" "rust_proc_macro"))
+
+;; pool() is handled specially since it's also a variable name
+(defvar gn-font-lock-buildfile-fun-keywords
+  '("assert" "config" "declare_args" "defined" "exec_script" "foreach"
+    "forward_variables_from" "get_label_info" "get_path_info"
+    "get_target_outputs" "getenv" "import" "not_needed" "print"
+    "process_file_template" "read_file" "rebase_path" "set_default_toolchain"
+    "set_defaults" "split_list" "string_join" "string_split" "template" "tool"
+    "toolchain" "propagates_configs" "write_file"))
+
+(defvar gn-font-lock-predefined-var-keywords
+  '("current_cpu" "current_os" "current_toolchain" "default_toolchain"
+    "host_cpu" "host_os" "invoker" "python_path" "root_build_dir" "root_gen_dir"
+    "root_out_dir" "target_cpu" "target_gen_dir" "target_name" "target_os"
+    "target_out_dir"))
+
+(defvar gn-font-lock-var-keywords
+  '("all_dependent_configs" "allow_circular_includes_from" "arflags" "args"
+    "asmflags" "assert_no_deps" "bundle_deps_filter" "bundle_executable_dir"
+    "bundle_resources_dir" "bundle_root_dir" "cflags" "cflags_c" "cflags_cc"
+    "cflags_objc" "cflags_objcc" "check_includes" "code_signing_args"
+    "code_signing_outputs" "code_signing_script" "code_signing_sources"
+    "complete_static_lib" "configs" "data" "data_deps" "defines" "depfile"
+    "deps" "framework_dir" "frameworks" "include_dirs" "inputs" "ldflags"
+    "lib_dirs" "libs" "output_dir" "output_extension" "output_name"
+    "output_prefix_override" "outputs" "pool" "precompiled_header"
+    "precompiled_header_type" "precompiled_source" "product_type" "public"
+    "public_configs" "public_deps" "response_file_contents" "script" "sources"
+    "testonly" "visibility" "write_runtime_deps" "bundle_contents_dir"
+    "contents" "output_conversion" "rebase" "data_keys" "walk_keys"))
+
+(defconst gn-font-lock-keywords
+  `((,(regexp-opt gn-font-lock-reserved-keywords 'words) .
+     font-lock-keyword-face)
+    (,(regexp-opt gn-font-lock-target-declaration-keywords 'words) .
+     font-lock-type-face)
+    (,(regexp-opt gn-font-lock-buildfile-fun-keywords 'words) .
+     font-lock-function-name-face)
+    ;; pool() as a function
+    ("\\<\\(pool\\)\\s-*("
+     (1 font-lock-function-name-face))
+    (,(regexp-opt gn-font-lock-predefined-var-keywords 'words) .
+     font-lock-constant-face)
+    (,(regexp-opt gn-font-lock-var-keywords 'words) .
+     font-lock-variable-name-face)
+    ;; $variables_like_this
+    ("\\(\\$\\)\\([a-zA-Z0-9_]+\\)"
+     (1 'gn-embedded-variable-boundary t)
+     (2 'gn-embedded-variable t))
+    ;; ${variables_like_this}
+    ("\\(\\${\\)\\([^\n }]+\\)\\(}\\)"
+     (1 'gn-embedded-variable-boundary t)
+     (2 'gn-embedded-variable t)
+     (3 'gn-embedded-variable-boundary t))
+    ;; {{placeholders}}    (see substitute_type.h)
+    ("\\({{\\)\\([^\n }]+\\)\\(}}\\)"
+     (1 'gn-embedded-variable-boundary t)
+     (2 'gn-embedded-variable t)
+     (3 'gn-embedded-variable-boundary t))))
+
+(defun gn-smie-rules (kind token)
+  "These are slightly modified indentation rules from the SMIE
+  Indentation Example info page. This changes the :before rule
+  and adds a :list-intro to handle our x = [ ] syntax."
+  (pcase (cons kind token)
+    (`(:elem . basic) gn-indent-basic)
+    (`(,_ . ",") (smie-rule-separator kind))
+    (`(:list-intro . "") gn-indent-basic)
+    (`(:before . ,(or `"[" `"(" `"{"))
+     (if (smie-rule-hanging-p) (smie-rule-parent)))
+    (`(:before . "if")
+     (and (not (smie-rule-bolp)) (smie-rule-prev-p "else")
+          (smie-rule-parent)))))
+
+(defun gn-fill-paragraph (&optional justify)
+  "We only fill inside of comments in GN mode."
+  (interactive "P")
+  (or (fill-comment-paragraph justify)
+      ;; Never return nil; `fill-paragraph' will perform its default behavior
+      ;; if we do.
+      t))
+
+(defun gn-run-format ()
+  "Run 'gn format' on the buffer in place."
+  (interactive)
+  ;; We can't `save-excursion' here; that will put us at the beginning of the
+  ;; shell output, aka the beginning of the document.
+  (let ((my-start-line (line-number-at-pos)))
+    (shell-command-on-region (point-min) (point-max) gn-format-command nil t)
+    (goto-line my-start-line)))
+
+(defvar gn-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\C-c\C-f" 'gn-run-format)
+    map))
+
+;;;###autoload
+(define-derived-mode gn-mode prog-mode "GN"
+  "Major mode for editing gn (Generate Ninja)."
+  :group 'gn
+
+  (setq-local comment-use-syntax t)
+  (setq-local comment-start "#")
+  (setq-local comment-end "")
+  (setq-local indent-tabs-mode nil)
+
+  (setq-local fill-paragraph-function 'gn-fill-paragraph)
+
+  (setq-local font-lock-defaults '(gn-font-lock-keywords))
+
+  ;; For every 'rule("name") {', adds "name" to the imenu for quick navigation.
+  (setq-local imenu-generic-expression
+              '((nil "^\s*[a-zA-Z0-9_]+(\"\\([a-zA-Z0-9_]+\\)\")\s*{" 1)))
+
+  (smie-setup nil #'gn-smie-rules)
+  (setq-local smie-indent-basic gn-indent-basic)
+
+  ;; python style comment: “# …”
+  (modify-syntax-entry ?# "< b" gn-mode-syntax-table)
+  (modify-syntax-entry ?\n "> b" gn-mode-syntax-table)
+  (modify-syntax-entry ?_ "w" gn-mode-syntax-table))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.gni?\\'" . gn-mode))
+
+(provide 'gn-mode)
diff --git a/misc/help_as_html.py b/misc/help_as_html.py
new file mode 100755 (executable)
index 0000000..f8f1c1b
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Runs 'gn help' and various subhelps, and spits out html.
+# TODO:
+# - Handle numbered and dashed lists -> <ol> <ul>. (See "os" and "toolchain").
+# - Handle "Arguments:" blocks a bit better (the argument names could be
+#   distinguished).
+# - Convert "|blahblah|" to <code>.
+# - Spit out other similar formats like wiki, markdown, whatever.
+
+import cgi
+import subprocess
+import sys
+
+
+def GetOutput(*args):
+  try:
+    return subprocess.check_output([sys.argv[1]] + list(args))
+  except subprocess.CalledProcessError:
+    return ''
+
+
+def ParseTopLevel(out):
+  commands = []
+  output = []
+  for line in out.splitlines():
+    if line.startswith('  '):
+      command, sep, rest = line.partition(':')
+      command = command.strip()
+      is_option = command.startswith('-')
+      output_line = ['<li>']
+      if not is_option:
+        commands.append(command)
+        output_line.append('<a href="#' + cgi.escape(command) + '">')
+      output_line.append(cgi.escape(command))
+      if not is_option:
+        output_line.append('</a>')
+      output_line.extend([sep + cgi.escape(rest) + '</li>'])
+      output.append(''.join(output_line))
+    else:
+      output.append('<h2>' + cgi.escape(line) + '</h2>')
+  return commands, output
+
+
+def ParseCommand(command, out):
+  first_line = True
+  got_example = False
+  output = []
+  for line in out.splitlines():
+    if first_line:
+      name, sep, rest = line.partition(':')
+      name = name.strip()
+      output.append('<h3><a name="' + cgi.escape(command) + '">' +
+                    cgi.escape(name + sep + rest) + '</a></h3>')
+      first_line = False
+    else:
+      if line.startswith('Example'):
+        # Special subsection that's pre-formatted.
+        if got_example:
+          output.append('</pre>')
+        got_example = True
+        output.append('<h4>Example</h4>')
+        output.append('<pre>')
+      elif not line.strip():
+        output.append('<p>')
+      elif not line.startswith('  ') and line.endswith(':'):
+        # Subsection.
+        output.append('<h4>' + cgi.escape(line[:-1]) + '</h4>')
+      else:
+        output.append(cgi.escape(line))
+  if got_example:
+    output.append('</pre>')
+  return output
+
+
+def main():
+  if len(sys.argv) < 2:
+    print 'usage: help_as_html.py <gn_binary>'
+    return 1
+  header = '''<!DOCTYPE html>
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <style>
+      body { font-family: Arial, sans-serif; font-size: small; }
+      pre { font-family: Consolas, monospace; font-size: small; }
+      #container { margin: 0 auto; max-width: 48rem; width: 90%; }
+    </style>
+  </head>
+  <body>
+    <div id="container"><h1>GN</h1>
+'''
+  footer = '</div></body></html>'
+  commands, output = ParseTopLevel(GetOutput('help'))
+  for command in commands:
+    output += ParseCommand(command, GetOutput('help', command))
+  print header + '\n'.join(output) + footer
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/misc/tm/GN.tmLanguage b/misc/tm/GN.tmLanguage
new file mode 100644 (file)
index 0000000..7ce3a5c
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>fileTypes</key>
+  <array>
+    <string>gn</string>
+    <string>gni</string>
+  </array>
+  <key>name</key>
+  <string>GN</string>
+  <key>patterns</key>
+  <array>
+    <dict>
+      <key>comment</key>
+      <string>keywords</string>
+      <key>match</key>
+      <string>\b(?:if|else)\b</string>
+      <key>name</key>
+      <string>keyword.control.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>constants</string>
+      <key>match</key>
+      <string>\b(?:true|false)\b</string>
+      <key>name</key>
+      <string>constant.language.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>numbers</string>
+      <key>match</key>
+      <string>\b\d+\.?(?:\d+)?\b</string>
+      <key>name</key>
+      <string>constant.numeric.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>double quoted string</string>
+      <key>match</key>
+      <string>\"[^\"]*\"</string>
+      <key>name</key>
+      <string>string.quoted.double.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>comment</string>
+      <key>begin</key>
+      <string>#</string>
+      <key>end</key>
+      <string>$</string>
+      <key>name</key>
+      <string>comment.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>operators</string>
+      <key>match</key>
+      <string>(?:=|==|\+=|-=|\+|-)</string>
+      <key>name</key>
+      <string>keyword.operator.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>targets</string>
+      <key>match</key>
+      <string>\b(?:action|action_foreach|copy|executable|group|loadable_module|shared_library|source_set|static_library|generated_file|rust_library|rust_proc_macro)\b</string>
+      <key>name</key>
+      <string>entity.name.tag.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>functions</string>
+      <key>match</key>
+      <string>\b(?:assert|config|declare_args|defined|exec_script|foreach|get_label_info|get_path_info|get_target_outputs|getenv|import|print|process_file_template|read_file|rebase_path|set_default_toolchain|set_defaults|split_list|string_join|string_split|template|tool|toolchain|toolchain_args|propagates_configs|write_file)\b</string>
+      <key>name</key>
+      <string>entity.name.function.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>predefined variables</string>
+      <key>match</key>
+      <string>\b(?:current_cpu|current_os|current_toolchain|default_toolchain|host_cpu|host_os|python_path|root_build_dir|root_gen_dir|root_out_dir|target_cpu|target_gen_dir|target_os|target_out_dir)\b</string>
+      <key>name</key>
+      <string>variable.parameter.gn</string>
+    </dict>
+    <dict>
+      <key>comment</key>
+      <string>target variables</string>
+      <key>match</key>
+      <string>\b(?:all_dependent_configs|allow_circular_includes_from|args|asmflags|cflags|cflags_c|cflags_cc|cflags_objc|cflags_objcc|check_includes|complete_static_lib|configs|data|data_deps|defines|depfile|deps|framework_dirs|frameworks|include_dirs|inputs|ldflags|lib_dirs|libs|output_extension|output_name|outputs|public|public_configs|public_deps|script|sources|testonly|visibility|contents|output_conversion|rebase|data_keys|walk_keys)\b</string>
+      <key>name</key>
+      <string>entity.other.attribute-name.gn</string>
+    </dict>
+  </array>
+  <key>scopeName</key>
+  <string>source.gn</string>
+  <key>uuid</key>
+  <string>DE419F8C-EC46-4824-87F3-732BD08694DC</string>
+</dict>
+</plist>
diff --git a/misc/tm/GN.tmPreferences b/misc/tm/GN.tmPreferences
new file mode 100644 (file)
index 0000000..2706d51
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+   <key>name</key>
+   <string>Comments</string>
+   <key>scope</key>
+   <string>source.gn</string>
+   <key>settings</key>
+   <dict>
+      <key>shellVariables</key>
+      <array>
+         <dict>
+            <key>name</key>
+            <string>TM_COMMENT_START</string>
+            <key>value</key>
+            <string># </string>
+         </dict>
+      </array>
+   </dict>
+</dict>
+</plist>
\ No newline at end of file
diff --git a/misc/vim/README.md b/misc/vim/README.md
new file mode 100644 (file)
index 0000000..26433f6
--- /dev/null
@@ -0,0 +1,65 @@
+# GN vim syntax plugin
+
+## Installation with a plugin manager
+
+You can use modern plugin managers to download the GN repo and manage the vim
+plugin:
+
+Example config for [vim-plug](https://github.com/junegunn/vim-plug):
+
+```
+Plug 'https://gn.googlesource.com/gn', { 'rtp': 'misc/vim' }
+```
+
+Or, for [Vundle](https://github.com/VundleVim/Vundle.vim) users:
+
+```
+Plugin 'https://gn.googlesource.com/gn', { 'rtp': 'misc/vim' }
+```
+
+## Manual installation
+
+If you don't use a plugin manager or would prefer to manage the GN repo
+yourself, you can add this explicitly to `rtp` in your `.vimrc`:
+
+```
+set runtimepath+=/path/to/gn/misc/vim
+" ...
+filetype plugin indent on " or a similar command to turn on filetypes in vim
+```
+
+## Formatting GN files
+
+### vim-codefmt (recommended)
+
+[vim-codefmt](https://github.com/google/vim-codefmt) supports the GN filetype
+natively. Add the following to your `.vimrc`:
+
+```vim
+" Install vim-codefmt and its dependencies
+Plug 'google/vim-maktaba'
+Plug 'google/vim-codefmt'
+
+" Install this plugin:
+Plug 'https://gn.googlesource.com/gn', { 'rtp': 'misc/vim' }
+
+" Optional: configure vim-codefmt to autoformat upon saving the buffer.
+augroup CodeFmt
+  autocmd!
+  autocmd FileType gn AutoFormatBuffer gn
+  " Other file types...
+augroup END
+```
+
+This will autoformat your files every time you save. If you prefer not to format
+files upon saving, vim-codefmt can format the buffer by calling `:FormatCode`.
+
+### Included format integration
+
+If you cannot include vim-codefmt, you can use the limited `gn format`
+integration included in this plugin. Add the following to your `.vimrc`:
+
+```vim
+" Replace <F1> with whichever hotkey you prefer:
+nnoremap <silent> <F1> :pyxf <path-to-this-plugin>/gn-format.py<CR>
+```
diff --git a/misc/vim/autoload/gn.vim b/misc/vim/autoload/gn.vim
new file mode 100644 (file)
index 0000000..5573efc
--- /dev/null
@@ -0,0 +1,26 @@
+" Copyright 2017 The Chromium Authors. All rights reserved.
+" Use of this source code is governed by a BSD-style license that can be
+" found in the LICENSE file.
+
+function! gn#TranslateToBuildFile(name) abort
+  " Strip '//' prefix
+  let l:new_path = substitute(a:name, '\v^//', '', '')
+
+  " Strip the build target name (necessary if 'isfname' contains ':')
+  let l:new_path = substitute(l:new_path, '\v:.*$', '', '')
+
+  " Append 'BUILD.gn', only if this is a directory and not a file
+  " Prefer using maktaba if it's available, but fallback to an alternative
+  if exists('*maktaba#path#Basename')
+    " Check if the last part of the path appears to be a file
+    if maktaba#path#Basename(l:new_path) !~# '\V.'
+      let l:new_path = maktaba#path#Join([l:new_path, 'BUILD.gn'])
+    endif
+  else
+    " This will break if 'autochdir' is enabled
+    if isdirectory(l:new_path)
+      let l:new_path = substitute(l:new_path, '\v/?$', '/BUILD.gn', '')
+    endif
+  endif
+  return l:new_path
+endfunction
diff --git a/misc/vim/ftdetect/gnfiletype.vim b/misc/vim/ftdetect/gnfiletype.vim
new file mode 100644 (file)
index 0000000..20448c1
--- /dev/null
@@ -0,0 +1,27 @@
+" Copyright 2014 The Chromium Authors. All rights reserved.
+" Use of this source code is governed by a BSD-style license that can be
+" found in the LICENSE file.
+
+" We take care to preserve the user's fileencodings and fileformats,
+" because those settings are global (not buffer local), yet we want
+" to override them for loading GN files, which should be UTF-8.
+let s:current_fileformats = ''
+let s:current_fileencodings = ''
+
+" define fileencodings to open as utf-8 encoding even if it's ascii.
+function! s:gnfiletype_pre()
+  let s:current_fileformats = &g:fileformats
+  let s:current_fileencodings = &g:fileencodings
+  set fileencodings=utf-8 fileformats=unix
+  setlocal filetype=gn
+endfunction
+
+" restore fileencodings as others
+function! s:gnfiletype_post()
+  let &g:fileformats = s:current_fileformats
+  let &g:fileencodings = s:current_fileencodings
+endfunction
+
+au BufNewFile *.gn,*.gni setlocal filetype=gn fileencoding=utf-8 fileformat=unix
+au BufRead *.gn,*.gni call s:gnfiletype_pre()
+au BufReadPost *.gn,*.gni call s:gnfiletype_post()
diff --git a/misc/vim/ftplugin/gn.vim b/misc/vim/ftplugin/gn.vim
new file mode 100644 (file)
index 0000000..ede251d
--- /dev/null
@@ -0,0 +1,12 @@
+" Copyright 2017 The Chromium Authors. All rights reserved.
+" Use of this source code is governed by a BSD-style license that can be
+" found in the LICENSE file.
+
+if exists('b:did_ftplugin')
+  finish
+endif
+let b:did_ftplugin = 1
+
+setlocal includeexpr=gn#TranslateToBuildFile(v:fname)
+
+setlocal commentstring=#\ %s
diff --git a/misc/vim/gn-format.py b/misc/vim/gn-format.py
new file mode 100644 (file)
index 0000000..7d07d43
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Based on clang-format.py.
+#
+# This file is a minimal gn format vim-integration. To install:
+# - Change 'binary' if gn is not on the path (see below).
+# - Add to your .vimrc:
+#
+#   map <F1> :pyxf <path-to-this-file>/gn-format.py<CR>
+#
+# gn format currently formats only a complete file so visual ranges, etc. won't
+# be used. It operates on the current, potentially unsaved buffer and does not
+# create or save any files. To revert a formatting, just undo.
+
+from __future__ import print_function
+import difflib
+import subprocess
+import sys
+import vim
+
+# Change this to the full path if gn is not on the path.
+binary = 'gn'
+if vim.eval('exists("g:gn_path")') == "1":
+  binary = vim.eval('g:gn_path')
+
+def main():
+  # Get the current text.
+  buf = vim.current.buffer
+  text = '\n'.join(buf)
+
+  is_win = sys.platform.startswith('win32')
+  # Avoid flashing an ugly cmd prompt on Windows when invoking gn.
+  startupinfo = None
+  if is_win:
+    startupinfo = subprocess.STARTUPINFO()
+    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+    startupinfo.wShowWindow = subprocess.SW_HIDE
+
+  # Call formatter. Needs shell=True on Windows due to gn.bat in depot_tools.
+  p = subprocess.Popen([binary, 'format', '--stdin'],
+                       stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                       stdin=subprocess.PIPE, startupinfo=startupinfo,
+                       shell=is_win, universal_newlines=True)
+  stdout, stderr = p.communicate(input=text)
+  if p.returncode != 0:
+    print('Formatting failed, please report to gn-dev@chromium.org.')
+    print(stdout, stderr)
+  else:
+    # Otherwise, replace current buffer.
+    lines = stdout.split('\n')
+    # Last line should have trailing \n, but we don't want to insert a blank
+    # line at the end of the buffer, so remove that.
+    if lines[-1] == '':
+      lines = lines[:-1]
+    sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines)
+    for op in reversed(sequence.get_opcodes()):
+      if op[0] != 'equal':
+        vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]]
+
+main()
diff --git a/misc/vim/syntax/gn.vim b/misc/vim/syntax/gn.vim
new file mode 100644 (file)
index 0000000..454aacc
--- /dev/null
@@ -0,0 +1,86 @@
+" Copyright 2014 The Chromium Authors. All rights reserved.
+" Use of this source code is governed by a BSD-style license that can be
+" found in the LICENSE file.
+"
+" gn.vim: Vim syntax file for GN.
+"
+" Quit when a (custom) syntax file was already loaded
+"if exists("b:current_syntax")
+  "finish
+"endif
+
+syn case match
+
+" Keywords within functions
+syn keyword     gnConditional       if else
+hi def link     gnConditional       Conditional
+
+" Predefined variables
+syn keyword     gnPredefVar current_cpu current_os current_toolchain
+syn keyword     gnPredefVar default_toolchain host_cpu host_os
+syn keyword     gnPredefVar root_build_dir root_gen_dir root_out_dir
+syn keyword     gnPredefVar target_cpu target_gen_dir target_out_dir
+syn keyword     gnPredefVar target_os
+syn keyword     gnPredefVar true false
+hi def link     gnPredefVar         Constant
+
+" Target declarations
+syn keyword     gnTarget action action_foreach copy executable group
+syn keyword     gnTarget shared_library source_set static_library
+syn keyword     gnTarget loadable_module generated_file
+syn keyword     gnTarget rust_library rust_proc_macro
+hi def link     gnTarget            Type
+
+" Buildfile functions
+syn keyword     gnFunctions assert config declare_args defined exec_script
+syn keyword     gnFunctions foreach get_label_info get_path_info
+syn keyword     gnFunctions get_target_outputs getenv import print
+syn keyword     gnFunctions process_file_template propagates_configs read_file
+syn keyword     gnFunctions rebase_path set_default_toolchain set_defaults
+syn keyword     gnFunctions split_list string_join string_split template tool
+syn keyword     gnFunctions toolchain toolchain_args write_file
+hi def link     gnFunctions         Macro
+
+" Variables
+syn keyword     gnVariable all_dependent_configs allow_circular_includes_from
+syn keyword     gnVariable args asmflags cflags cflags_c cflags_cc cflags_objc
+syn keyword     gnVariable cflags_objcc check_includes complete_static_lib
+syn keyword     gnVariable configs data data_deps defines depfile deps
+syn keyword     gnVariable framework_dirs frameworks include_dirs inputs ldflags
+syn keyword     gnVariable lib_dirs libs output_extension output_name outputs
+syn keyword     gnVariable public public_configs public_deps scripte sources
+syn keyword     gnVariable testonly visibility contents output_conversion rebase
+syn keyword     gnVariable data_keys walk_keys
+hi def link     gnVariable          Keyword
+
+" Strings
+syn region      gnString start=+L\="+ skip=+\\\\\|\\"+ end=+"+ contains=@Spell,gnTargetName
+syn match       gnTargetName '\v:[^"]+' contained
+hi def link     gnString            String
+hi def link     gnTargetName        Special
+
+" Comments
+syn keyword     gnTodo              contained TODO FIXME XXX BUG NOTE
+syn cluster     gnCommentGroup      contains=gnTodo
+syn region      gnComment           start="#" end="$" contains=@gnCommentGroup,@Spell
+
+hi def link     gnComment           Comment
+hi def link     gnTodo              Todo
+
+" Operators; I think this is a bit too colourful.
+"syn match gnOperator /=/
+"syn match gnOperator /!=/
+"syn match gnOperator />=/
+"syn match gnOperator /<=/
+"syn match gnOperator /==/
+"syn match gnOperator /+=/
+"syn match gnOperator /-=/
+"syn match gnOperator /\s>\s/
+"syn match gnOperator /\s<\s/
+"syn match gnOperator /\s+\s/
+"syn match gnOperator /\s-\s/
+"hi def link     gnOperator          Operator
+
+syn sync minlines=500
+
+let b:current_syntax = "gn"
diff --git a/src/base/atomic_ref_count.h b/src/base/atomic_ref_count.h
new file mode 100644 (file)
index 0000000..7ebef16
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a low level implementation of atomic semantics for reference
+// counting.  Please use base/memory/ref_counted.h directly instead.
+
+#ifndef BASE_ATOMIC_REF_COUNT_H_
+#define BASE_ATOMIC_REF_COUNT_H_
+
+#include <atomic>
+
+namespace base {
+
+class AtomicRefCount {
+ public:
+  constexpr AtomicRefCount() : ref_count_(0) {}
+  explicit constexpr AtomicRefCount(int initial_value)
+      : ref_count_(initial_value) {}
+
+  // Increment a reference count.
+  void Increment() { Increment(1); }
+
+  // Increment a reference count by "increment", which must exceed 0.
+  void Increment(int increment) {
+    ref_count_.fetch_add(increment, std::memory_order_relaxed);
+  }
+
+  // Decrement a reference count, and return whether the result is non-zero.
+  // Insert barriers to ensure that state written before the reference count
+  // became zero will be visible to a thread that has just made the count zero.
+  bool Decrement() {
+    // TODO(jbroman): Technically this doesn't need to be an acquire operation
+    // unless the result is 1 (i.e., the ref count did indeed reach zero).
+    // However, there are toolchain issues that make that not work as well at
+    // present (notably TSAN doesn't like it).
+    return ref_count_.fetch_sub(1, std::memory_order_acq_rel) != 1;
+  }
+
+  // Return whether the reference count is one.  If the reference count is used
+  // in the conventional way, a reference count of 1 implies that the current
+  // thread owns the reference and no other thread shares it.  This call
+  // performs the test for a reference count of one, and performs the memory
+  // barrier needed for the owning thread to act on the object, knowing that it
+  // has exclusive access to the object.
+  bool IsOne() const { return ref_count_.load(std::memory_order_acquire) == 1; }
+
+  // Return whether the reference count is zero.  With conventional object
+  // referencing counting, the object will be destroyed, so the reference count
+  // should never be zero.  Hence this is generally used for a debug check.
+  bool IsZero() const {
+    return ref_count_.load(std::memory_order_acquire) == 0;
+  }
+
+  // Returns the current reference count (with no barriers). This is subtle, and
+  // should be used only for debugging.
+  int SubtleRefCountForDebug() const {
+    return ref_count_.load(std::memory_order_relaxed);
+  }
+
+ private:
+  std::atomic_int ref_count_;
+};
+
+}  // namespace base
+
+#endif  // BASE_ATOMIC_REF_COUNT_H_
diff --git a/src/base/command_line.cc b/src/base/command_line.cc
new file mode 100644 (file)
index 0000000..ea4bbda
--- /dev/null
@@ -0,0 +1,491 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+
+#include <algorithm>
+#include <iterator>
+#include <ostream>
+#include <string_view>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include <shellapi.h>
+#endif
+
+namespace base {
+
+CommandLine* CommandLine::current_process_commandline_ = nullptr;
+
+namespace {
+
+const CommandLine::CharType kSwitchTerminator[] = FILE_PATH_LITERAL("--");
+const CommandLine::CharType kSwitchValueSeparator[] = FILE_PATH_LITERAL("=");
+
+// Since we use a lazy match, make sure that longer versions (like "--") are
+// listed before shorter versions (like "-") of similar prefixes.
+#if defined(OS_WIN)
+// By putting slash last, we can control whether it is treaded as a switch
+// value by changing the value of switch_prefix_count to be one less than
+// the array size.
+const CommandLine::CharType* const kSwitchPrefixes[] = {u"--", u"-", u"/"};
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+// Unixes don't use slash as a switch.
+const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"};
+#endif
+size_t switch_prefix_count = std::size(kSwitchPrefixes);
+
+size_t GetSwitchPrefixLength(const CommandLine::StringType& string) {
+  for (size_t i = 0; i < switch_prefix_count; ++i) {
+    CommandLine::StringType prefix(kSwitchPrefixes[i]);
+    if (string.compare(0, prefix.length(), prefix) == 0)
+      return prefix.length();
+  }
+  return 0;
+}
+
+// Fills in |switch_string| and |switch_value| if |string| is a switch.
+// This will preserve the input switch prefix in the output |switch_string|.
+bool IsSwitch(const CommandLine::StringType& string,
+              CommandLine::StringType* switch_string,
+              CommandLine::StringType* switch_value) {
+  switch_string->clear();
+  switch_value->clear();
+  size_t prefix_length = GetSwitchPrefixLength(string);
+  if (prefix_length == 0 || prefix_length == string.length())
+    return false;
+
+  const size_t equals_position = string.find(kSwitchValueSeparator);
+  *switch_string = string.substr(0, equals_position);
+  if (equals_position != CommandLine::StringType::npos)
+    *switch_value = string.substr(equals_position + 1);
+  return true;
+}
+
+// Append switches and arguments, keeping switches before arguments
+// if handle_switches is true.
+void AppendSwitchesAndArguments(CommandLine* command_line,
+                                const CommandLine::StringVector& argv,
+                                bool handle_switches) {
+  bool parse_switches = handle_switches;
+  for (size_t i = 1; i < argv.size(); ++i) {
+    CommandLine::StringType arg = argv[i];
+#if defined(OS_WIN)
+    TrimWhitespace(arg, TRIM_ALL, &arg);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    TrimWhitespaceASCII(arg, TRIM_ALL, &arg);
+#endif
+
+    CommandLine::StringType switch_string;
+    CommandLine::StringType switch_value;
+    parse_switches &= (arg != kSwitchTerminator);
+    if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
+#if defined(OS_WIN)
+      command_line->AppendSwitchNative(UTF16ToASCII(switch_string),
+                                       switch_value);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+      command_line->AppendSwitchNative(switch_string, switch_value);
+#else
+#error Unsupported platform
+#endif
+    } else {
+      command_line->AppendArgNative(arg);
+    }
+  }
+}
+
+#if defined(OS_WIN)
+// Quote a string as necessary for CommandLineToArgvW compatibility *on Windows*.
+std::u16string QuoteForCommandLineToArgvW(const std::u16string& arg,
+                                          bool quote_placeholders) {
+  // We follow the quoting rules of CommandLineToArgvW.
+  // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
+  std::u16string quotable_chars(u" \\\"");
+  // We may also be required to quote '%', which is commonly used in a command
+  // line as a placeholder. (It may be substituted for a string with spaces.)
+  if (quote_placeholders)
+    quotable_chars.push_back('%');
+  if (arg.find_first_of(quotable_chars) == std::u16string::npos) {
+    // No quoting necessary.
+    return arg;
+  }
+
+  std::u16string out;
+  out.push_back('"');
+  for (size_t i = 0; i < arg.size(); ++i) {
+    if (arg[i] == '\\') {
+      // Find the extent of this run of backslashes.
+      size_t start = i, end = start + 1;
+      for (; end < arg.size() && arg[end] == '\\'; ++end) {
+      }
+      size_t backslash_count = end - start;
+
+      // Backslashes are escapes only if the run is followed by a double quote.
+      // Since we also will end the string with a double quote, we escape for
+      // either a double quote or the end of the string.
+      if (end == arg.size() || arg[end] == '"') {
+        // To quote, we need to output 2x as many backslashes.
+        backslash_count *= 2;
+      }
+      for (size_t j = 0; j < backslash_count; ++j)
+        out.push_back('\\');
+
+      // Advance i to one before the end to balance i++ in loop.
+      i = end - 1;
+    } else if (arg[i] == '"') {
+      out.push_back('\\');
+      out.push_back('"');
+    } else {
+      out.push_back(arg[i]);
+    }
+  }
+  out.push_back('"');
+
+  return out;
+}
+#endif
+
+}  // namespace
+
+CommandLine::CommandLine(NoProgram no_program)
+    : argv_(1), begin_args_(1), parse_switches_(true) {}
+
+CommandLine::CommandLine(const FilePath& program)
+    : argv_(1), begin_args_(1), parse_switches_(true) {
+  SetProgram(program);
+}
+
+CommandLine::CommandLine(int argc, const CommandLine::CharType* const* argv)
+    : argv_(1), begin_args_(1), parse_switches_(true) {
+  InitFromArgv(argc, argv);
+}
+
+CommandLine::CommandLine(const StringVector& argv)
+    : argv_(1), begin_args_(1), parse_switches_(true) {
+  InitFromArgv(argv);
+}
+
+CommandLine::CommandLine(const CommandLine& other) = default;
+
+CommandLine& CommandLine::operator=(const CommandLine& other) = default;
+
+CommandLine::~CommandLine() = default;
+
+#if defined(OS_WIN)
+// static
+void CommandLine::set_slash_is_not_a_switch() {
+  // The last switch prefix should be slash, so adjust the size to skip it.
+  DCHECK(std::u16string_view(kSwitchPrefixes[std::size(kSwitchPrefixes) - 1]) ==
+         std::u16string_view(u"/"));
+  switch_prefix_count = std::size(kSwitchPrefixes) - 1;
+}
+
+// static
+void CommandLine::InitUsingArgvForTesting(int argc, const char* const* argv) {
+  DCHECK(!current_process_commandline_);
+  current_process_commandline_ = new CommandLine(NO_PROGRAM);
+  // On Windows we need to convert the command line arguments to std::u16string.
+  base::CommandLine::StringVector argv_vector;
+  for (int i = 0; i < argc; ++i)
+    argv_vector.push_back(UTF8ToUTF16(argv[i]));
+  current_process_commandline_->InitFromArgv(argv_vector);
+}
+#endif
+
+// static
+bool CommandLine::Init(int argc, const char* const* argv) {
+  if (current_process_commandline_) {
+    // If this is intentional, Reset() must be called first. If we are using
+    // the shared build mode, we have to share a single object across multiple
+    // shared libraries.
+    return false;
+  }
+
+  current_process_commandline_ = new CommandLine(NO_PROGRAM);
+#if defined(OS_WIN)
+  current_process_commandline_->ParseFromString(
+      reinterpret_cast<const char16_t*>(::GetCommandLineW()));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  current_process_commandline_->InitFromArgv(argc, argv);
+#else
+#error Unsupported platform
+#endif
+
+  return true;
+}
+
+// static
+void CommandLine::Reset() {
+  DCHECK(current_process_commandline_);
+  delete current_process_commandline_;
+  current_process_commandline_ = nullptr;
+}
+
+// static
+CommandLine* CommandLine::ForCurrentProcess() {
+  DCHECK(current_process_commandline_);
+  return current_process_commandline_;
+}
+
+// static
+bool CommandLine::InitializedForCurrentProcess() {
+  return !!current_process_commandline_;
+}
+
+#if defined(OS_WIN)
+// static
+CommandLine CommandLine::FromString(const std::u16string& command_line) {
+  CommandLine cmd(NO_PROGRAM);
+  cmd.ParseFromString(command_line);
+  return cmd;
+}
+#endif
+
+void CommandLine::InitFromArgv(int argc,
+                               const CommandLine::CharType* const* argv) {
+  StringVector new_argv;
+  for (int i = 0; i < argc; ++i)
+    new_argv.push_back(argv[i]);
+  InitFromArgv(new_argv);
+}
+
+void CommandLine::InitFromArgv(const StringVector& argv) {
+  argv_ = StringVector(1);
+  switches_.clear();
+  begin_args_ = 1;
+  SetProgram(argv.empty() ? FilePath() : FilePath(argv[0]));
+  AppendSwitchesAndArguments(this, argv, parse_switches_);
+}
+
+FilePath CommandLine::GetProgram() const {
+  return FilePath(argv_[0]);
+}
+
+void CommandLine::SetProgram(const FilePath& program) {
+#if defined(OS_WIN)
+  TrimWhitespace(program.value(), TRIM_ALL, &argv_[0]);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  TrimWhitespaceASCII(program.value(), TRIM_ALL, &argv_[0]);
+#else
+#error Unsupported platform
+#endif
+}
+
+bool CommandLine::HasSwitch(const std::string_view& switch_string) const {
+  DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
+  return ContainsKey(switches_, switch_string);
+}
+
+bool CommandLine::HasSwitch(const char switch_constant[]) const {
+  return HasSwitch(std::string_view(switch_constant));
+}
+
+std::string CommandLine::GetSwitchValueASCII(
+    const std::string_view& switch_string) const {
+  StringType value = GetSwitchValueNative(switch_string);
+  if (!IsStringASCII(value)) {
+    DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII.";
+    return std::string();
+  }
+#if defined(OS_WIN)
+  return UTF16ToASCII(value);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  return value;
+#endif
+}
+
+FilePath CommandLine::GetSwitchValuePath(
+    const std::string_view& switch_string) const {
+  return FilePath(GetSwitchValueNative(switch_string));
+}
+
+CommandLine::StringType CommandLine::GetSwitchValueNative(
+    const std::string_view& switch_string) const {
+  DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
+  auto result = switches_.find(switch_string);
+  return result == switches_.end() ? StringType() : result->second;
+}
+
+void CommandLine::AppendSwitch(const std::string& switch_string) {
+  AppendSwitchNative(switch_string, StringType());
+}
+
+void CommandLine::AppendSwitchPath(const std::string& switch_string,
+                                   const FilePath& path) {
+  AppendSwitchNative(switch_string, path.value());
+}
+
+void CommandLine::AppendSwitchNative(const std::string& switch_string,
+                                     const CommandLine::StringType& value) {
+#if defined(OS_WIN)
+  const std::string switch_key = ToLowerASCII(switch_string);
+  StringType combined_switch_string(ASCIIToUTF16(switch_key));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  const std::string& switch_key = switch_string;
+  StringType combined_switch_string(switch_key);
+#endif
+  size_t prefix_length = GetSwitchPrefixLength(combined_switch_string);
+  auto insertion =
+      switches_.insert(make_pair(switch_key.substr(prefix_length), value));
+  if (!insertion.second)
+    insertion.first->second = value;
+  // Preserve existing switch prefixes in |argv_|; only append one if necessary.
+  if (prefix_length == 0)
+    combined_switch_string = kSwitchPrefixes[0] + combined_switch_string;
+  if (!value.empty())
+    combined_switch_string += kSwitchValueSeparator + value;
+  // Append the switch and update the switches/arguments divider |begin_args_|.
+  argv_.insert(argv_.begin() + begin_args_++, combined_switch_string);
+}
+
+void CommandLine::AppendSwitchASCII(const std::string& switch_string,
+                                    const std::string& value_string) {
+#if defined(OS_WIN)
+  AppendSwitchNative(switch_string, ASCIIToUTF16(value_string));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  AppendSwitchNative(switch_string, value_string);
+#else
+#error Unsupported platform
+#endif
+}
+
+void CommandLine::CopySwitchesFrom(const CommandLine& source,
+                                   const char* const switches[],
+                                   size_t count) {
+  for (size_t i = 0; i < count; ++i) {
+    if (source.HasSwitch(switches[i]))
+      AppendSwitchNative(switches[i], source.GetSwitchValueNative(switches[i]));
+  }
+}
+
+CommandLine::StringVector CommandLine::GetArgs() const {
+  // Gather all arguments after the last switch (may include kSwitchTerminator).
+  StringVector args(argv_.begin() + begin_args_, argv_.end());
+  // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?)
+  StringVector::iterator switch_terminator =
+      std::find(args.begin(), args.end(), kSwitchTerminator);
+  if (switch_terminator != args.end())
+    args.erase(switch_terminator);
+  return args;
+}
+
+void CommandLine::AppendArg(const std::string& value) {
+#if defined(OS_WIN)
+  DCHECK(IsStringUTF8(value));
+  AppendArgNative(UTF8ToUTF16(value));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  AppendArgNative(value);
+#else
+#error Unsupported platform
+#endif
+}
+
+void CommandLine::AppendArgPath(const FilePath& path) {
+  AppendArgNative(path.value());
+}
+
+void CommandLine::AppendArgNative(const CommandLine::StringType& value) {
+  argv_.push_back(value);
+}
+
+void CommandLine::AppendArguments(const CommandLine& other,
+                                  bool include_program) {
+  if (include_program)
+    SetProgram(other.GetProgram());
+  AppendSwitchesAndArguments(this, other.argv(), parse_switches_);
+}
+
+void CommandLine::PrependWrapper(const CommandLine::StringType& wrapper) {
+  if (wrapper.empty())
+    return;
+  // Split the wrapper command based on whitespace (with quoting).
+  using CommandLineTokenizer =
+      StringTokenizerT<StringType, StringType::const_iterator>;
+  CommandLineTokenizer tokenizer(wrapper, FILE_PATH_LITERAL(" "));
+  tokenizer.set_quote_chars(FILE_PATH_LITERAL("'\""));
+  std::vector<StringType> wrapper_argv;
+  while (tokenizer.GetNext())
+    wrapper_argv.emplace_back(tokenizer.token());
+
+  // Prepend the wrapper and update the switches/arguments |begin_args_|.
+  argv_.insert(argv_.begin(), wrapper_argv.begin(), wrapper_argv.end());
+  begin_args_ += wrapper_argv.size();
+}
+
+#if defined(OS_WIN)
+void CommandLine::ParseFromString(const std::u16string& command_line) {
+  std::u16string command_line_string;
+  TrimWhitespace(command_line, TRIM_ALL, &command_line_string);
+  if (command_line_string.empty())
+    return;
+
+  int num_args = 0;
+  char16_t** args = NULL;
+  args = reinterpret_cast<char16_t**>(::CommandLineToArgvW(
+      reinterpret_cast<LPCWSTR>(command_line_string.c_str()), &num_args));
+
+  DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: "
+                         << UTF16ToUTF8(command_line);
+  InitFromArgv(num_args, args);
+  LocalFree(args);
+}
+#endif
+
+CommandLine::StringType CommandLine::GetCommandLineStringInternal(
+    bool quote_placeholders) const {
+  StringType string(argv_[0]);
+#if defined(OS_WIN)
+  string = QuoteForCommandLineToArgvW(string, quote_placeholders);
+#endif
+  StringType params(GetArgumentsStringInternal(quote_placeholders));
+  if (!params.empty()) {
+    string.append(StringType(FILE_PATH_LITERAL(" ")));
+    string.append(params);
+  }
+  return string;
+}
+
+CommandLine::StringType CommandLine::GetArgumentsStringInternal(
+    bool quote_placeholders) const {
+  StringType params;
+  // Append switches and arguments.
+  bool parse_switches = parse_switches_;
+  for (size_t i = 1; i < argv_.size(); ++i) {
+    StringType arg = argv_[i];
+    StringType switch_string;
+    StringType switch_value;
+    parse_switches &= arg != kSwitchTerminator;
+    if (i > 1)
+      params.append(StringType(FILE_PATH_LITERAL(" ")));
+    if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
+      params.append(switch_string);
+      if (!switch_value.empty()) {
+#if defined(OS_WIN)
+        switch_value =
+            QuoteForCommandLineToArgvW(switch_value, quote_placeholders);
+#endif
+        params.append(kSwitchValueSeparator + switch_value);
+      }
+    } else {
+#if defined(OS_WIN)
+      arg = QuoteForCommandLineToArgvW(arg, quote_placeholders);
+#endif
+      params.append(arg);
+    }
+  }
+  return params;
+}
+
+}  // namespace base
diff --git a/src/base/command_line.h b/src/base/command_line.h
new file mode 100644 (file)
index 0000000..46c1fcc
--- /dev/null
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This class works with command lines: building and parsing.
+// Arguments with prefixes ('--', '-', and on Windows, '/') are switches.
+// Switches will precede all other arguments without switch prefixes.
+// Switches can optionally have values, delimited by '=', e.g., "-switch=value".
+// An argument of "--" will terminate switch parsing during initialization,
+// interpreting subsequent tokens as non-switch arguments, regardless of prefix.
+
+// There is a singleton read-only CommandLine that represents the command line
+// that the current process was started with.  It must be initialized in main().
+
+#ifndef BASE_COMMAND_LINE_H_
+#define BASE_COMMAND_LINE_H_
+
+#include <stddef.h>
+#include <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "util/build_config.h"
+
+namespace base {
+
+class FilePath;
+
+class CommandLine {
+ public:
+#if defined(OS_WIN)
+  // The native command line string type.
+  using StringType = std::u16string;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  using StringType = std::string;
+#endif
+
+  using CharType = StringType::value_type;
+  using StringVector = std::vector<StringType>;
+  using SwitchMap = std::map<std::string, StringType, std::less<>>;
+
+  // A constructor for CommandLines that only carry switches and arguments.
+  enum NoProgram { NO_PROGRAM };
+  explicit CommandLine(NoProgram no_program);
+
+  // Construct a new command line with |program| as argv[0].
+  explicit CommandLine(const FilePath& program);
+
+  // Construct a new command line from an argument list.
+  CommandLine(int argc, const CharType* const* argv);
+  explicit CommandLine(const StringVector& argv);
+
+  // Override copy and assign to ensure |switches_by_stringpiece_| is valid.
+  CommandLine(const CommandLine& other);
+  CommandLine& operator=(const CommandLine& other);
+
+  ~CommandLine();
+
+#if defined(OS_WIN)
+  // By default this class will treat command-line arguments beginning with
+  // slashes as switches on Windows, but not other platforms.
+  //
+  // If this behavior is inappropriate for your application, you can call this
+  // function BEFORE initializing the current process' global command line
+  // object and the behavior will be the same as Posix systems (only hyphens
+  // begin switches, everything else will be an arg).
+  static void set_slash_is_not_a_switch();
+
+  // Normally when the CommandLine singleton is initialized it gets the command
+  // line via the GetCommandLineW API and then uses the shell32 API
+  // CommandLineToArgvW to parse the command line and convert it back to
+  // argc and argv. Tests who don't want this dependency on shell32 and need
+  // to honor the arguments passed in should use this function.
+  static void InitUsingArgvForTesting(int argc, const char* const* argv);
+#endif
+
+  // Initialize the current process CommandLine singleton. On Windows, ignores
+  // its arguments (we instead parse GetCommandLineW() directly) because we
+  // don't trust the CRT's parsing of the command line, but it still must be
+  // called to set up the command line. Returns false if initialization has
+  // already occurred, and true otherwise. Only the caller receiving a 'true'
+  // return value should take responsibility for calling Reset.
+  static bool Init(int argc, const char* const* argv);
+
+  // Destroys the current process CommandLine singleton. This is necessary if
+  // you want to reset the base library to its initial state (for example, in an
+  // outer library that needs to be able to terminate, and be re-initialized).
+  // If Init is called only once, as in main(), Reset() is not necessary.
+  // Do not call this in tests. Use base::test::ScopedCommandLine instead.
+  static void Reset();
+
+  // Get the singleton CommandLine representing the current process's
+  // command line. Note: returned value is mutable, but not thread safe;
+  // only mutate if you know what you're doing!
+  static CommandLine* ForCurrentProcess();
+
+  // Returns true if the CommandLine has been initialized for the given process.
+  static bool InitializedForCurrentProcess();
+
+#if defined(OS_WIN)
+  static CommandLine FromString(const std::u16string& command_line);
+#endif
+
+  // Initialize from an argv vector.
+  void InitFromArgv(int argc, const CharType* const* argv);
+  void InitFromArgv(const StringVector& argv);
+
+  // Constructs and returns the represented command line string.
+  // CAUTION! This should be avoided on POSIX because quoting behavior is
+  // unclear.
+  StringType GetCommandLineString() const {
+    return GetCommandLineStringInternal(false);
+  }
+
+#if defined(OS_WIN)
+  // Constructs and returns the represented command line string. Assumes the
+  // command line contains placeholders (eg, %1) and quotes any program or
+  // argument with a '%' in it. This should be avoided unless the placeholder is
+  // required by an external interface (eg, the Windows registry), because it is
+  // not generally safe to replace it with an arbitrary string. If possible,
+  // placeholders should be replaced *before* converting the command line to a
+  // string.
+  StringType GetCommandLineStringWithPlaceholders() const {
+    return GetCommandLineStringInternal(true);
+  }
+#endif
+
+  // Constructs and returns the represented arguments string.
+  // CAUTION! This should be avoided on POSIX because quoting behavior is
+  // unclear.
+  StringType GetArgumentsString() const {
+    return GetArgumentsStringInternal(false);
+  }
+
+#if defined(OS_WIN)
+  // Constructs and returns the represented arguments string. Assumes the
+  // command line contains placeholders (eg, %1) and quotes any argument with a
+  // '%' in it. This should be avoided unless the placeholder is required by an
+  // external interface (eg, the Windows registry), because it is not generally
+  // safe to replace it with an arbitrary string. If possible, placeholders
+  // should be replaced *before* converting the arguments to a string.
+  StringType GetArgumentsStringWithPlaceholders() const {
+    return GetArgumentsStringInternal(true);
+  }
+#endif
+
+  // Returns the original command line string as a vector of strings.
+  const StringVector& argv() const { return argv_; }
+
+  // Get and Set the program part of the command line string (the first item).
+  FilePath GetProgram() const;
+  void SetProgram(const FilePath& program);
+
+  // Enables/disables the parsing of switches for future argument appending.
+  // True by default, but can be set to false to ensure that no re-ordering
+  // is done.
+  void SetParseSwitches(bool parse_switches) {
+    parse_switches_ = parse_switches;
+  }
+
+  // Returns true if this command line contains the given switch.
+  // Switch names must be lowercase.
+  // The second override provides an optimized version to avoid inlining codegen
+  // at every callsite to find the length of the constant and construct a
+  // std::string_view.
+  bool HasSwitch(const std::string_view& switch_string) const;
+  bool HasSwitch(const char switch_constant[]) const;
+
+  // Returns the value associated with the given switch. If the switch has no
+  // value or isn't present, this method returns the empty string.
+  // Switch names must be lowercase.
+  std::string GetSwitchValueASCII(const std::string_view& switch_string) const;
+  FilePath GetSwitchValuePath(const std::string_view& switch_string) const;
+  StringType GetSwitchValueNative(const std::string_view& switch_string) const;
+
+  // Get a copy of all switches, along with their values.
+  const SwitchMap& GetSwitches() const { return switches_; }
+
+  // Append a switch [with optional value] to the command line.
+  // Note: Switches will precede arguments regardless of appending order.
+  void AppendSwitch(const std::string& switch_string);
+  void AppendSwitchPath(const std::string& switch_string, const FilePath& path);
+  void AppendSwitchNative(const std::string& switch_string,
+                          const StringType& value);
+  void AppendSwitchASCII(const std::string& switch_string,
+                         const std::string& value);
+
+  // Copy a set of switches (and any values) from another command line.
+  // Commonly used when launching a subprocess.
+  void CopySwitchesFrom(const CommandLine& source,
+                        const char* const switches[],
+                        size_t count);
+
+  // Get the remaining arguments to the command.
+  StringVector GetArgs() const;
+
+  // Append an argument to the command line. Note that the argument is quoted
+  // properly such that it is interpreted as one argument to the target command.
+  // AppendArg is primarily for ASCII; non-ASCII input is interpreted as UTF-8.
+  // Note: Switches will precede arguments regardless of appending order.
+  void AppendArg(const std::string& value);
+  void AppendArgPath(const FilePath& value);
+  void AppendArgNative(const StringType& value);
+
+  // Append the switches and arguments from another command line to this one.
+  // If |include_program| is true, include |other|'s program as well.
+  void AppendArguments(const CommandLine& other, bool include_program);
+
+  // Insert a command before the current command.
+  // Common for debuggers, like "gdb --args".
+  void PrependWrapper(const StringType& wrapper);
+
+#if defined(OS_WIN)
+  // Initialize by parsing the given command line string.
+  // The program name is assumed to be the first item in the string.
+  void ParseFromString(const std::u16string& command_line);
+#endif
+
+ private:
+  // Disallow default constructor; a program name must be explicitly specified.
+  CommandLine() = delete;
+  // Allow the copy constructor. A common pattern is to copy of the current
+  // process's command line and then add some flags to it. For example:
+  //   CommandLine cl(*CommandLine::ForCurrentProcess());
+  //   cl.AppendSwitch(...);
+
+  // Internal version of GetCommandLineString. If |quote_placeholders| is true,
+  // also quotes parts with '%' in them.
+  StringType GetCommandLineStringInternal(bool quote_placeholders) const;
+
+  // Internal version of GetArgumentsString. If |quote_placeholders| is true,
+  // also quotes parts with '%' in them.
+  StringType GetArgumentsStringInternal(bool quote_placeholders) const;
+
+  // The singleton CommandLine representing the current process's command line.
+  static CommandLine* current_process_commandline_;
+
+  // The argv array: { program, [(--|-|/)switch[=value]]*, [--], [argument]* }
+  StringVector argv_;
+
+  // Parsed-out switch keys and values.
+  SwitchMap switches_;
+
+  // The index after the program and switches, any arguments start here.
+  size_t begin_args_;
+
+  // Whether or not to parse arguments that look like switches as switches.
+  bool parse_switches_;
+};
+
+}  // namespace base
+
+#endif  // BASE_COMMAND_LINE_H_
diff --git a/src/base/compiler_specific.h b/src/base/compiler_specific.h
new file mode 100644 (file)
index 0000000..75f21e8
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_COMPILER_SPECIFIC_H_
+#define BASE_COMPILER_SPECIFIC_H_
+
+#include "util/build_config.h"
+
+#if defined(COMPILER_MSVC)
+
+// For _Printf_format_string_.
+#include <sal.h>
+
+#else  // Not MSVC
+
+#define _Printf_format_string_
+
+#endif  // COMPILER_MSVC
+
+#if COMPILER_GCC && defined(NDEBUG)
+#define ALWAYS_INLINE inline __attribute__((__always_inline__))
+#elif COMPILER_MSVC && defined(NDEBUG)
+#define ALWAYS_INLINE __forceinline
+#else
+#define ALWAYS_INLINE inline
+#endif
+
+// Annotate a function indicating the caller must examine the return value.
+// Use like:
+//   int foo() WARN_UNUSED_RESULT;
+// To explicitly ignore a result, see |ignore_result()| in base/macros.h.
+#undef WARN_UNUSED_RESULT
+#if defined(COMPILER_GCC) || defined(__clang__)
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+#define WARN_UNUSED_RESULT
+#endif
+
+// Tell the compiler a function is using a printf-style format string.
+// |format_param| is the one-based index of the format string parameter;
+// |dots_param| is the one-based index of the "..." parameter.
+// For v*printf functions (which take a va_list), pass 0 for dots_param.
+// (This is undocumented but matches what the system C headers do.)
+#if defined(COMPILER_GCC) || defined(__clang__)
+#define PRINTF_FORMAT(format_param, dots_param) \
+  __attribute__((format(printf, format_param, dots_param)))
+#else
+#define PRINTF_FORMAT(format_param, dots_param)
+#endif
+
+// Macro for hinting that an expression is likely to be false.
+#if !defined(UNLIKELY)
+#if defined(COMPILER_GCC) || defined(__clang__)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define UNLIKELY(x) (x)
+#endif  // defined(COMPILER_GCC)
+#endif  // !defined(UNLIKELY)
+
+#if !defined(LIKELY)
+#if defined(COMPILER_GCC) || defined(__clang__)
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#else
+#define LIKELY(x) (x)
+#endif  // defined(COMPILER_GCC)
+#endif  // !defined(LIKELY)
+
+// Macro for telling -Wimplicit-fallthrough that a fallthrough is intentional.
+#if defined(__clang__)
+#define FALLTHROUGH [[clang::fallthrough]]
+#else
+#define FALLTHROUGH
+#endif
+
+#endif  // BASE_COMPILER_SPECIFIC_H_
diff --git a/src/base/containers/circular_deque.h b/src/base/containers/circular_deque.h
new file mode 100644 (file)
index 0000000..2048336
--- /dev/null
@@ -0,0 +1,1111 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_CIRCULAR_DEQUE_H_
+#define BASE_CONTAINERS_CIRCULAR_DEQUE_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "base/containers/vector_buffer.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/template_util.h"
+
+// base::circular_deque is similar to std::deque. Unlike std::deque, the
+// storage is provided in a flat circular buffer conceptually similar to a
+// vector. The beginning and end will wrap around as necessary so that
+// pushes and pops will be constant time as long as a capacity expansion is
+// not required.
+//
+// The API should be identical to std::deque with the following differences:
+//
+//  - ITERATORS ARE NOT STABLE. Mutating the container will invalidate all
+//    iterators.
+//
+//  - Insertions may resize the vector and so are not constant time (std::deque
+//    guarantees constant time for insertions at the ends).
+//
+//  - Container-wide comparisons are not implemented. If you want to compare
+//    two containers, use an algorithm so the expensive iteration is explicit.
+//
+// If you want a similar container with only a queue API, use base::queue in
+// base/containers/queue.h.
+//
+// Constructors:
+//   circular_deque();
+//   circular_deque(size_t count);
+//   circular_deque(size_t count, const T& value);
+//   circular_deque(InputIterator first, InputIterator last);
+//   circular_deque(const circular_deque&);
+//   circular_deque(circular_deque&&);
+//   circular_deque(std::initializer_list<value_type>);
+//
+// Assignment functions:
+//   circular_deque& operator=(const circular_deque&);
+//   circular_deque& operator=(circular_deque&&);
+//   circular_deque& operator=(std::initializer_list<T>);
+//   void assign(size_t count, const T& value);
+//   void assign(InputIterator first, InputIterator last);
+//   void assign(std::initializer_list<T> value);
+//
+// Random accessors:
+//   T& at(size_t);
+//   const T& at(size_t) const;
+//   T& operator[](size_t);
+//   const T& operator[](size_t) const;
+//
+// End accessors:
+//   T& front();
+//   const T& front() const;
+//   T& back();
+//   const T& back() const;
+//
+// Iterator functions:
+//   iterator               begin();
+//   const_iterator         begin() const;
+//   const_iterator         cbegin() const;
+//   iterator               end();
+//   const_iterator         end() const;
+//   const_iterator         cend() const;
+//   reverse_iterator       rbegin();
+//   const_reverse_iterator rbegin() const;
+//   const_reverse_iterator crbegin() const;
+//   reverse_iterator       rend();
+//   const_reverse_iterator rend() const;
+//   const_reverse_iterator crend() const;
+//
+// Memory management:
+//   void reserve(size_t);  // SEE IMPLEMENTATION FOR SOME GOTCHAS.
+//   size_t capacity() const;
+//   void shrink_to_fit();
+//
+// Size management:
+//   void clear();
+//   bool empty() const;
+//   size_t size() const;
+//   void resize(size_t);
+//   void resize(size_t count, const T& value);
+//
+// Positional insert and erase:
+//   void insert(const_iterator pos, size_type count, const T& value);
+//   void insert(const_iterator pos,
+//               InputIterator first, InputIterator last);
+//   iterator insert(const_iterator pos, const T& value);
+//   iterator insert(const_iterator pos, T&& value);
+//   iterator emplace(const_iterator pos, Args&&... args);
+//   iterator erase(const_iterator pos);
+//   iterator erase(const_iterator first, const_iterator last);
+//
+// End insert and erase:
+//   void push_front(const T&);
+//   void push_front(T&&);
+//   void push_back(const T&);
+//   void push_back(T&&);
+//   T& emplace_front(Args&&...);
+//   T& emplace_back(Args&&...);
+//   void pop_front();
+//   void pop_back();
+//
+// General:
+//   void swap(circular_deque&);
+
+namespace base {
+
+template <class T>
+class circular_deque;
+
+namespace internal {
+
+// Start allocating nonempty buffers with this many entries. This is the
+// external capacity so the internal buffer will be one larger (= 4) which is
+// more even for the allocator. See the descriptions of internal vs. external
+// capacity on the comment above the buffer_ variable below.
+constexpr size_t kCircularBufferInitialCapacity = 3;
+
+template <typename T>
+class circular_deque_const_iterator {
+ public:
+  using difference_type = std::ptrdiff_t;
+  using value_type = T;
+  using pointer = const T*;
+  using reference = const T&;
+  using iterator_category = std::random_access_iterator_tag;
+
+  circular_deque_const_iterator() : parent_deque_(nullptr), index_(0) {
+#if DCHECK_IS_ON()
+    created_generation_ = 0;
+#endif  // DCHECK_IS_ON()
+  }
+
+  // Dereferencing.
+  const T& operator*() const {
+    CheckUnstableUsage();
+    parent_deque_->CheckValidIndex(index_);
+    return parent_deque_->buffer_[index_];
+  }
+  const T* operator->() const {
+    CheckUnstableUsage();
+    parent_deque_->CheckValidIndex(index_);
+    return &parent_deque_->buffer_[index_];
+  }
+  const value_type& operator[](difference_type i) const { return *(*this + i); }
+
+  // Increment and decrement.
+  circular_deque_const_iterator& operator++() {
+    Increment();
+    return *this;
+  }
+  circular_deque_const_iterator operator++(int) {
+    circular_deque_const_iterator ret = *this;
+    Increment();
+    return ret;
+  }
+  circular_deque_const_iterator& operator--() {
+    Decrement();
+    return *this;
+  }
+  circular_deque_const_iterator operator--(int) {
+    circular_deque_const_iterator ret = *this;
+    Decrement();
+    return ret;
+  }
+
+  // Random access mutation.
+  friend circular_deque_const_iterator operator+(
+      const circular_deque_const_iterator& iter,
+      difference_type offset) {
+    circular_deque_const_iterator ret = iter;
+    ret.Add(offset);
+    return ret;
+  }
+  circular_deque_const_iterator& operator+=(difference_type offset) {
+    Add(offset);
+    return *this;
+  }
+  friend circular_deque_const_iterator operator-(
+      const circular_deque_const_iterator& iter,
+      difference_type offset) {
+    circular_deque_const_iterator ret = iter;
+    ret.Add(-offset);
+    return ret;
+  }
+  circular_deque_const_iterator& operator-=(difference_type offset) {
+    Add(-offset);
+    return *this;
+  }
+
+  friend std::ptrdiff_t operator-(const circular_deque_const_iterator& lhs,
+                                  const circular_deque_const_iterator& rhs) {
+    lhs.CheckComparable(rhs);
+    return lhs.OffsetFromBegin() - rhs.OffsetFromBegin();
+  }
+
+  // Comparisons.
+  friend bool operator==(const circular_deque_const_iterator& lhs,
+                         const circular_deque_const_iterator& rhs) {
+    lhs.CheckComparable(rhs);
+    return lhs.index_ == rhs.index_;
+  }
+  friend bool operator!=(const circular_deque_const_iterator& lhs,
+                         const circular_deque_const_iterator& rhs) {
+    return !(lhs == rhs);
+  }
+  friend bool operator<(const circular_deque_const_iterator& lhs,
+                        const circular_deque_const_iterator& rhs) {
+    lhs.CheckComparable(rhs);
+    return lhs.OffsetFromBegin() < rhs.OffsetFromBegin();
+  }
+  friend bool operator<=(const circular_deque_const_iterator& lhs,
+                         const circular_deque_const_iterator& rhs) {
+    return !(lhs > rhs);
+  }
+  friend bool operator>(const circular_deque_const_iterator& lhs,
+                        const circular_deque_const_iterator& rhs) {
+    lhs.CheckComparable(rhs);
+    return lhs.OffsetFromBegin() > rhs.OffsetFromBegin();
+  }
+  friend bool operator>=(const circular_deque_const_iterator& lhs,
+                         const circular_deque_const_iterator& rhs) {
+    return !(lhs < rhs);
+  }
+
+ protected:
+  friend class circular_deque<T>;
+
+  circular_deque_const_iterator(const circular_deque<T>* parent, size_t index)
+      : parent_deque_(parent), index_(index) {
+#if DCHECK_IS_ON()
+    created_generation_ = parent->generation_;
+#endif  // DCHECK_IS_ON()
+  }
+
+  // Returns the offset from the beginning index of the buffer to the current
+  // item.
+  size_t OffsetFromBegin() const {
+    if (index_ >= parent_deque_->begin_)
+      return index_ - parent_deque_->begin_;  // On the same side as begin.
+    return parent_deque_->buffer_.capacity() - parent_deque_->begin_ + index_;
+  }
+
+  // Most uses will be ++ and -- so use a simplified implementation.
+  void Increment() {
+    CheckUnstableUsage();
+    parent_deque_->CheckValidIndex(index_);
+    index_++;
+    if (index_ == parent_deque_->buffer_.capacity())
+      index_ = 0;
+  }
+  void Decrement() {
+    CheckUnstableUsage();
+    parent_deque_->CheckValidIndexOrEnd(index_);
+    if (index_ == 0)
+      index_ = parent_deque_->buffer_.capacity() - 1;
+    else
+      index_--;
+  }
+  void Add(difference_type delta) {
+    CheckUnstableUsage();
+#if DCHECK_IS_ON()
+    if (delta <= 0)
+      parent_deque_->CheckValidIndexOrEnd(index_);
+    else
+      parent_deque_->CheckValidIndex(index_);
+#endif
+    // It should be valid to add 0 to any iterator, even if the container is
+    // empty and the iterator points to end(). The modulo below will divide
+    // by 0 if the buffer capacity is empty, so it's important to check for
+    // this case explicitly.
+    if (delta == 0)
+      return;
+
+    difference_type new_offset = OffsetFromBegin() + delta;
+    DCHECK(new_offset >= 0 &&
+           new_offset <= static_cast<difference_type>(parent_deque_->size()));
+    index_ = (new_offset + parent_deque_->begin_) %
+             parent_deque_->buffer_.capacity();
+  }
+
+#if DCHECK_IS_ON()
+  void CheckUnstableUsage() const {
+    DCHECK(parent_deque_);
+    // Since circular_deque doesn't guarantee stability, any attempt to
+    // dereference this iterator after a mutation (i.e. the generation doesn't
+    // match the original) in the container is illegal.
+    DCHECK_EQ(created_generation_, parent_deque_->generation_)
+        << "circular_deque iterator dereferenced after mutation.";
+  }
+  void CheckComparable(const circular_deque_const_iterator& other) const {
+    DCHECK_EQ(parent_deque_, other.parent_deque_);
+    // Since circular_deque doesn't guarantee stability, two iterators that
+    // are compared must have been generated without mutating the container.
+    // If this fires, the container was mutated between generating the two
+    // iterators being compared.
+    DCHECK_EQ(created_generation_, other.created_generation_);
+  }
+#else
+  inline void CheckUnstableUsage() const {}
+  inline void CheckComparable(const circular_deque_const_iterator&) const {}
+#endif  // DCHECK_IS_ON()
+
+  const circular_deque<T>* parent_deque_;
+  size_t index_;
+
+#if DCHECK_IS_ON()
+  // The generation of the parent deque when this iterator was created. The
+  // container will update the generation for every modification so we can
+  // test if the container was modified by comparing them.
+  uint64_t created_generation_;
+#endif  // DCHECK_IS_ON()
+};
+
+template <typename T>
+class circular_deque_iterator : public circular_deque_const_iterator<T> {
+  using base = circular_deque_const_iterator<T>;
+
+ public:
+  friend class circular_deque<T>;
+
+  using difference_type = std::ptrdiff_t;
+  using value_type = T;
+  using pointer = T*;
+  using reference = T&;
+  using iterator_category = std::random_access_iterator_tag;
+
+  // Expose the base class' constructor.
+  circular_deque_iterator() : circular_deque_const_iterator<T>() {}
+
+  // Dereferencing.
+  T& operator*() const { return const_cast<T&>(base::operator*()); }
+  T* operator->() const { return const_cast<T*>(base::operator->()); }
+  T& operator[](difference_type i) {
+    return const_cast<T&>(base::operator[](i));
+  }
+
+  // Random access mutation.
+  friend circular_deque_iterator operator+(const circular_deque_iterator& iter,
+                                           difference_type offset) {
+    circular_deque_iterator ret = iter;
+    ret.Add(offset);
+    return ret;
+  }
+  circular_deque_iterator& operator+=(difference_type offset) {
+    base::Add(offset);
+    return *this;
+  }
+  friend circular_deque_iterator operator-(const circular_deque_iterator& iter,
+                                           difference_type offset) {
+    circular_deque_iterator ret = iter;
+    ret.Add(-offset);
+    return ret;
+  }
+  circular_deque_iterator& operator-=(difference_type offset) {
+    base::Add(-offset);
+    return *this;
+  }
+
+  // Increment and decrement.
+  circular_deque_iterator& operator++() {
+    base::Increment();
+    return *this;
+  }
+  circular_deque_iterator operator++(int) {
+    circular_deque_iterator ret = *this;
+    base::Increment();
+    return ret;
+  }
+  circular_deque_iterator& operator--() {
+    base::Decrement();
+    return *this;
+  }
+  circular_deque_iterator operator--(int) {
+    circular_deque_iterator ret = *this;
+    base::Decrement();
+    return ret;
+  }
+
+ private:
+  circular_deque_iterator(const circular_deque<T>* parent, size_t index)
+      : circular_deque_const_iterator<T>(parent, index) {}
+};
+
+}  // namespace internal
+
+template <typename T>
+class circular_deque {
+ private:
+  using VectorBuffer = internal::VectorBuffer<T>;
+
+ public:
+  using value_type = T;
+  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*;
+
+  using iterator = internal::circular_deque_iterator<T>;
+  using const_iterator = internal::circular_deque_const_iterator<T>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  // ---------------------------------------------------------------------------
+  // Constructor
+
+  constexpr circular_deque() = default;
+
+  // Constructs with |count| copies of |value| or default constructed version.
+  circular_deque(size_type count) { resize(count); }
+  circular_deque(size_type count, const T& value) { resize(count, value); }
+
+  // Range constructor.
+  template <class InputIterator>
+  circular_deque(InputIterator first, InputIterator last) {
+    assign(first, last);
+  }
+
+  // Copy/move.
+  circular_deque(const circular_deque& other) : buffer_(other.size() + 1) {
+    assign(other.begin(), other.end());
+  }
+  circular_deque(circular_deque&& other) noexcept
+      : buffer_(std::move(other.buffer_)),
+        begin_(other.begin_),
+        end_(other.end_) {
+    other.begin_ = 0;
+    other.end_ = 0;
+  }
+
+  circular_deque(std::initializer_list<value_type> init) { assign(init); }
+
+  ~circular_deque() { DestructRange(begin_, end_); }
+
+  // ---------------------------------------------------------------------------
+  // Assignments.
+  //
+  // All of these may invalidate iterators and references.
+
+  circular_deque& operator=(const circular_deque& other) {
+    if (&other == this)
+      return *this;
+
+    reserve(other.size());
+    assign(other.begin(), other.end());
+    return *this;
+  }
+  circular_deque& operator=(circular_deque&& other) noexcept {
+    if (&other == this)
+      return *this;
+
+    // We're about to overwrite the buffer, so don't free it in clear to
+    // avoid doing it twice.
+    ClearRetainCapacity();
+    buffer_ = std::move(other.buffer_);
+    begin_ = other.begin_;
+    end_ = other.end_;
+
+    other.begin_ = 0;
+    other.end_ = 0;
+
+    IncrementGeneration();
+    return *this;
+  }
+  circular_deque& operator=(std::initializer_list<value_type> ilist) {
+    reserve(ilist.size());
+    assign(std::begin(ilist), std::end(ilist));
+    return *this;
+  }
+
+  void assign(size_type count, const value_type& value) {
+    ClearRetainCapacity();
+    reserve(count);
+    for (size_t i = 0; i < count; i++)
+      emplace_back(value);
+    IncrementGeneration();
+  }
+
+  // This variant should be enabled only when InputIterator is an iterator.
+  template <typename InputIterator>
+  typename std::enable_if<::base::internal::is_iterator<InputIterator>::value,
+                          void>::type
+  assign(InputIterator first, InputIterator last) {
+    // Possible future enhancement, dispatch on iterator tag type. For forward
+    // iterators we can use std::difference to preallocate the space required
+    // and only do one copy.
+    ClearRetainCapacity();
+    for (; first != last; ++first)
+      emplace_back(*first);
+    IncrementGeneration();
+  }
+
+  void assign(std::initializer_list<value_type> value) {
+    reserve(std::distance(value.begin(), value.end()));
+    assign(value.begin(), value.end());
+  }
+
+  // ---------------------------------------------------------------------------
+  // Accessors.
+  //
+  // Since this class assumes no exceptions, at() and operator[] are equivalent.
+
+  const value_type& at(size_type i) const {
+    DCHECK(i < size());
+    size_t right_size = buffer_.capacity() - begin_;
+    if (begin_ <= end_ || i < right_size)
+      return buffer_[begin_ + i];
+    return buffer_[i - right_size];
+  }
+  value_type& at(size_type i) {
+    return const_cast<value_type&>(
+        const_cast<const circular_deque*>(this)->at(i));
+  }
+
+  value_type& operator[](size_type i) { return at(i); }
+  const value_type& operator[](size_type i) const {
+    return const_cast<circular_deque*>(this)->at(i);
+  }
+
+  value_type& front() {
+    DCHECK(!empty());
+    return buffer_[begin_];
+  }
+  const value_type& front() const {
+    DCHECK(!empty());
+    return buffer_[begin_];
+  }
+
+  value_type& back() {
+    DCHECK(!empty());
+    return *(--end());
+  }
+  const value_type& back() const {
+    DCHECK(!empty());
+    return *(--end());
+  }
+
+  // ---------------------------------------------------------------------------
+  // Iterators.
+
+  iterator begin() { return iterator(this, begin_); }
+  const_iterator begin() const { return const_iterator(this, begin_); }
+  const_iterator cbegin() const { return const_iterator(this, begin_); }
+
+  iterator end() { return iterator(this, end_); }
+  const_iterator end() const { return const_iterator(this, end_); }
+  const_iterator cend() const { return const_iterator(this, end_); }
+
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator crbegin() const { return rbegin(); }
+
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+  const_reverse_iterator crend() const { return rend(); }
+
+  // ---------------------------------------------------------------------------
+  // Memory management.
+
+  // IMPORTANT NOTE ON reserve(...): This class implements auto-shrinking of
+  // the buffer when elements are deleted and there is "too much" wasted space.
+  // So if you call reserve() with a large size in anticipation of pushing many
+  // elements, but pop an element before the queue is full, the capacity you
+  // reserved may be lost.
+  //
+  // As a result, it's only worthwhile to call reserve() when you're adding
+  // many things at once with no intermediate operations.
+  void reserve(size_type new_capacity) {
+    if (new_capacity > capacity())
+      SetCapacityTo(new_capacity);
+  }
+
+  size_type capacity() const {
+    // One item is wasted to indicate end().
+    return buffer_.capacity() == 0 ? 0 : buffer_.capacity() - 1;
+  }
+
+  void shrink_to_fit() {
+    if (empty()) {
+      // Optimize empty case to really delete everything if there was
+      // something.
+      if (buffer_.capacity())
+        buffer_ = VectorBuffer();
+    } else {
+      SetCapacityTo(size());
+    }
+  }
+
+  // ---------------------------------------------------------------------------
+  // Size management.
+
+  // This will additionally reset the capacity() to 0.
+  void clear() {
+    // This can't resize(0) because that requires a default constructor to
+    // compile, which not all contained classes may implement.
+    ClearRetainCapacity();
+    buffer_ = VectorBuffer();
+  }
+
+  bool empty() const { return begin_ == end_; }
+
+  size_type size() const {
+    if (begin_ <= end_)
+      return end_ - begin_;
+    return buffer_.capacity() - begin_ + end_;
+  }
+
+  // When reducing size, the elements are deleted from the end. When expanding
+  // size, elements are added to the end with |value| or the default
+  // constructed version. Even when using resize(count) to shrink, a default
+  // constructor is required for the code to compile, even though it will not
+  // be called.
+  //
+  // There are two versions rather than using a default value to avoid
+  // creating a temporary when shrinking (when it's not needed). Plus if
+  // the default constructor is desired when expanding usually just calling it
+  // for each element is faster than making a default-constructed temporary and
+  // copying it.
+  void resize(size_type count) {
+    // SEE BELOW VERSION if you change this. The code is mostly the same.
+    if (count > size()) {
+      // This could be slightly more efficient but expanding a queue with
+      // identical elements is unusual and the extra computations of emplacing
+      // one-by-one will typically be small relative to calling the constructor
+      // for every item.
+      ExpandCapacityIfNecessary(count - size());
+      while (size() < count)
+        emplace_back();
+    } else if (count < size()) {
+      size_t new_end = (begin_ + count) % buffer_.capacity();
+      DestructRange(new_end, end_);
+      end_ = new_end;
+
+      ShrinkCapacityIfNecessary();
+    }
+    IncrementGeneration();
+  }
+  void resize(size_type count, const value_type& value) {
+    // SEE ABOVE VERSION if you change this. The code is mostly the same.
+    if (count > size()) {
+      ExpandCapacityIfNecessary(count - size());
+      while (size() < count)
+        emplace_back(value);
+    } else if (count < size()) {
+      size_t new_end = (begin_ + count) % buffer_.capacity();
+      DestructRange(new_end, end_);
+      end_ = new_end;
+
+      ShrinkCapacityIfNecessary();
+    }
+    IncrementGeneration();
+  }
+
+  // ---------------------------------------------------------------------------
+  // Insert and erase.
+  //
+  // Insertion and deletion in the middle is O(n) and invalidates all existing
+  // iterators.
+  //
+  // The implementation of insert isn't optimized as much as it could be. If
+  // the insertion requires that the buffer be grown, it will first be grown
+  // and everything moved, and then the items will be inserted, potentially
+  // moving some items twice. This simplifies the implementation substantially
+  // and means less generated templatized code. Since this is an uncommon
+  // operation for deques, and already relatively slow, it doesn't seem worth
+  // the benefit to optimize this.
+
+  void insert(const_iterator pos, size_type count, const T& value) {
+    ValidateIterator(pos);
+
+    // Optimize insert at the beginning.
+    if (pos == begin()) {
+      ExpandCapacityIfNecessary(count);
+      for (size_t i = 0; i < count; i++)
+        push_front(value);
+      return;
+    }
+
+    iterator insert_cur(this, pos.index_);
+    iterator insert_end;
+    MakeRoomFor(count, &insert_cur, &insert_end);
+    while (insert_cur < insert_end) {
+      new (&buffer_[insert_cur.index_]) T(value);
+      ++insert_cur;
+    }
+
+    IncrementGeneration();
+  }
+
+  // This enable_if keeps this call from getting confused with the (pos, count,
+  // value) version when value is an integer.
+  template <class InputIterator>
+  typename std::enable_if<::base::internal::is_iterator<InputIterator>::value,
+                          void>::type
+  insert(const_iterator pos, InputIterator first, InputIterator last) {
+    ValidateIterator(pos);
+
+    size_t inserted_items = std::distance(first, last);
+    if (inserted_items == 0)
+      return;  // Can divide by 0 when doing modulo below, so return early.
+
+    // Make a hole to copy the items into.
+    iterator insert_cur;
+    iterator insert_end;
+    if (pos == begin()) {
+      // Optimize insert at the beginning, nothing needs to be shifted and the
+      // hole is the |inserted_items| block immediately before |begin_|.
+      ExpandCapacityIfNecessary(inserted_items);
+      insert_end = begin();
+      begin_ =
+          (begin_ + buffer_.capacity() - inserted_items) % buffer_.capacity();
+      insert_cur = begin();
+    } else {
+      insert_cur = iterator(this, pos.index_);
+      MakeRoomFor(inserted_items, &insert_cur, &insert_end);
+    }
+
+    // Copy the items.
+    while (insert_cur < insert_end) {
+      new (&buffer_[insert_cur.index_]) T(*first);
+      ++insert_cur;
+      ++first;
+    }
+
+    IncrementGeneration();
+  }
+
+  // These all return an iterator to the inserted item. Existing iterators will
+  // be invalidated.
+  iterator insert(const_iterator pos, const T& value) {
+    return emplace(pos, value);
+  }
+  iterator insert(const_iterator pos, T&& value) {
+    return emplace(pos, std::move(value));
+  }
+  template <class... Args>
+  iterator emplace(const_iterator pos, Args&&... args) {
+    ValidateIterator(pos);
+
+    // Optimize insert at beginning which doesn't require shifting.
+    if (pos == cbegin()) {
+      emplace_front(std::forward<Args>(args)...);
+      return begin();
+    }
+
+    // Do this before we make the new iterators we return.
+    IncrementGeneration();
+
+    iterator insert_begin(this, pos.index_);
+    iterator insert_end;
+    MakeRoomFor(1, &insert_begin, &insert_end);
+    new (&buffer_[insert_begin.index_]) T(std::forward<Args>(args)...);
+
+    return insert_begin;
+  }
+
+  // Calling erase() won't automatically resize the buffer smaller like resize
+  // or the pop functions. Erase is slow and relatively uncommon, and for
+  // normal deque usage a pop will normally be done on a regular basis that
+  // will prevent excessive buffer usage over long periods of time. It's not
+  // worth having the extra code for every template instantiation of erase()
+  // to resize capacity downward to a new buffer.
+  iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
+  iterator erase(const_iterator first, const_iterator last) {
+    ValidateIterator(first);
+    ValidateIterator(last);
+
+    IncrementGeneration();
+
+    // First, call the destructor on the deleted items.
+    if (first.index_ == last.index_) {
+      // Nothing deleted. Need to return early to avoid falling through to
+      // moving items on top of themselves.
+      return iterator(this, first.index_);
+    } else if (first.index_ < last.index_) {
+      // Contiguous range.
+      buffer_.DestructRange(&buffer_[first.index_], &buffer_[last.index_]);
+    } else {
+      // Deleted range wraps around.
+      buffer_.DestructRange(&buffer_[first.index_],
+                            &buffer_[buffer_.capacity()]);
+      buffer_.DestructRange(&buffer_[0], &buffer_[last.index_]);
+    }
+
+    if (first.index_ == begin_) {
+      // This deletion is from the beginning. Nothing needs to be copied, only
+      // begin_ needs to be updated.
+      begin_ = last.index_;
+      return iterator(this, last.index_);
+    }
+
+    // In an erase operation, the shifted items all move logically to the left,
+    // so move them from left-to-right.
+    iterator move_src(this, last.index_);
+    iterator move_src_end = end();
+    iterator move_dest(this, first.index_);
+    for (; move_src < move_src_end; move_src++, move_dest++) {
+      buffer_.MoveRange(&buffer_[move_src.index_],
+                        &buffer_[move_src.index_ + 1],
+                        &buffer_[move_dest.index_]);
+    }
+
+    end_ = move_dest.index_;
+
+    // Since we did not reallocate and only changed things after the erase
+    // element(s), the input iterator's index points to the thing following the
+    // deletion.
+    return iterator(this, first.index_);
+  }
+
+  // ---------------------------------------------------------------------------
+  // Begin/end operations.
+
+  void push_front(const T& value) { emplace_front(value); }
+  void push_front(T&& value) { emplace_front(std::move(value)); }
+
+  void push_back(const T& value) { emplace_back(value); }
+  void push_back(T&& value) { emplace_back(std::move(value)); }
+
+  template <class... Args>
+  reference emplace_front(Args&&... args) {
+    ExpandCapacityIfNecessary(1);
+    if (begin_ == 0)
+      begin_ = buffer_.capacity() - 1;
+    else
+      begin_--;
+    IncrementGeneration();
+    new (&buffer_[begin_]) T(std::forward<Args>(args)...);
+    return front();
+  }
+
+  template <class... Args>
+  reference emplace_back(Args&&... args) {
+    ExpandCapacityIfNecessary(1);
+    new (&buffer_[end_]) T(std::forward<Args>(args)...);
+    if (end_ == buffer_.capacity() - 1)
+      end_ = 0;
+    else
+      end_++;
+    IncrementGeneration();
+    return back();
+  }
+
+  void pop_front() {
+    DCHECK(size());
+    buffer_.DestructRange(&buffer_[begin_], &buffer_[begin_ + 1]);
+    begin_++;
+    if (begin_ == buffer_.capacity())
+      begin_ = 0;
+
+    ShrinkCapacityIfNecessary();
+
+    // Technically popping will not invalidate any iterators since the
+    // underlying buffer will be stable. But in the future we may want to add a
+    // feature that resizes the buffer smaller if there is too much wasted
+    // space. This ensures we can make such a change safely.
+    IncrementGeneration();
+  }
+  void pop_back() {
+    DCHECK(size());
+    if (end_ == 0)
+      end_ = buffer_.capacity() - 1;
+    else
+      end_--;
+    buffer_.DestructRange(&buffer_[end_], &buffer_[end_ + 1]);
+
+    ShrinkCapacityIfNecessary();
+
+    // See pop_front comment about why this is here.
+    IncrementGeneration();
+  }
+
+  // ---------------------------------------------------------------------------
+  // General operations.
+
+  void swap(circular_deque& other) {
+    std::swap(buffer_, other.buffer_);
+    std::swap(begin_, other.begin_);
+    std::swap(end_, other.end_);
+    IncrementGeneration();
+  }
+
+  friend void swap(circular_deque& lhs, circular_deque& rhs) { lhs.swap(rhs); }
+
+ private:
+  friend internal::circular_deque_iterator<T>;
+  friend internal::circular_deque_const_iterator<T>;
+
+  // Moves the items in the given circular buffer to the current one. The
+  // source is moved from so will become invalid. The destination buffer must
+  // have already been allocated with enough size.
+  static void MoveBuffer(VectorBuffer& from_buf,
+                         size_t from_begin,
+                         size_t from_end,
+                         VectorBuffer* to_buf,
+                         size_t* to_begin,
+                         size_t* to_end) {
+    size_t from_capacity = from_buf.capacity();
+
+    *to_begin = 0;
+    if (from_begin < from_end) {
+      // Contiguous.
+      from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_end],
+                         to_buf->begin());
+      *to_end = from_end - from_begin;
+    } else if (from_begin > from_end) {
+      // Discontiguous, copy the right side to the beginning of the new buffer.
+      from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_capacity],
+                         to_buf->begin());
+      size_t right_size = from_capacity - from_begin;
+      // Append the left side.
+      from_buf.MoveRange(&from_buf[0], &from_buf[from_end],
+                         &(*to_buf)[right_size]);
+      *to_end = right_size + from_end;
+    } else {
+      // No items.
+      *to_end = 0;
+    }
+  }
+
+  // Expands the buffer size. This assumes the size is larger than the
+  // number of elements in the vector (it won't call delete on anything).
+  void SetCapacityTo(size_t new_capacity) {
+    // Use the capacity + 1 as the internal buffer size to differentiate
+    // empty and full (see definition of buffer_ below).
+    VectorBuffer new_buffer(new_capacity + 1);
+    MoveBuffer(buffer_, begin_, end_, &new_buffer, &begin_, &end_);
+    buffer_ = std::move(new_buffer);
+  }
+  void ExpandCapacityIfNecessary(size_t additional_elts) {
+    size_t min_new_capacity = size() + additional_elts;
+    if (capacity() >= min_new_capacity)
+      return;  // Already enough room.
+
+    min_new_capacity =
+        std::max(min_new_capacity, internal::kCircularBufferInitialCapacity);
+
+    // std::vector always grows by at least 50%. WTF::Deque grows by at least
+    // 25%. We expect queue workloads to generally stay at a similar size and
+    // grow less than a vector might, so use 25%.
+    size_t new_capacity =
+        std::max(min_new_capacity, capacity() + capacity() / 4);
+    SetCapacityTo(new_capacity);
+  }
+
+  void ShrinkCapacityIfNecessary() {
+    // Don't auto-shrink below this size.
+    if (capacity() <= internal::kCircularBufferInitialCapacity)
+      return;
+
+    // Shrink when 100% of the size() is wasted.
+    size_t sz = size();
+    size_t empty_spaces = capacity() - sz;
+    if (empty_spaces < sz)
+      return;
+
+    // Leave 1/4 the size as free capacity, not going below the initial
+    // capacity.
+    size_t new_capacity =
+        std::max(internal::kCircularBufferInitialCapacity, sz + sz / 4);
+    if (new_capacity < capacity()) {
+      // Count extra item to convert to internal capacity.
+      SetCapacityTo(new_capacity);
+    }
+  }
+
+  // Backend for clear() but does not resize the internal buffer.
+  void ClearRetainCapacity() {
+    // This can't resize(0) because that requires a default constructor to
+    // compile, which not all contained classes may implement.
+    DestructRange(begin_, end_);
+    begin_ = 0;
+    end_ = 0;
+    IncrementGeneration();
+  }
+
+  // Calls destructors for the given begin->end indices. The indices may wrap
+  // around. The buffer is not resized, and the begin_ and end_ members are
+  // not changed.
+  void DestructRange(size_t begin, size_t end) {
+    if (end == begin) {
+      return;
+    } else if (end > begin) {
+      buffer_.DestructRange(&buffer_[begin], &buffer_[end]);
+    } else {
+      buffer_.DestructRange(&buffer_[begin], &buffer_[buffer_.capacity()]);
+      buffer_.DestructRange(&buffer_[0], &buffer_[end]);
+    }
+  }
+
+  // Makes room for |count| items starting at |*insert_begin|. Since iterators
+  // are not stable across buffer resizes, |*insert_begin| will be updated to
+  // point to the beginning of the newly opened position in the new array (it's
+  // in/out), and the end of the newly opened position (it's out-only).
+  void MakeRoomFor(size_t count, iterator* insert_begin, iterator* insert_end) {
+    if (count == 0) {
+      *insert_end = *insert_begin;
+      return;
+    }
+
+    // The offset from the beginning will be stable across reallocations.
+    size_t begin_offset = insert_begin->OffsetFromBegin();
+    ExpandCapacityIfNecessary(count);
+
+    insert_begin->index_ = (begin_ + begin_offset) % buffer_.capacity();
+    *insert_end =
+        iterator(this, (insert_begin->index_ + count) % buffer_.capacity());
+
+    // Update the new end and prepare the iterators for copying.
+    iterator src = end();
+    end_ = (end_ + count) % buffer_.capacity();
+    iterator dest = end();
+
+    // Move the elements. This will always involve shifting logically to the
+    // right, so move in a right-to-left order.
+    while (true) {
+      if (src == *insert_begin)
+        break;
+      --src;
+      --dest;
+      buffer_.MoveRange(&buffer_[src.index_], &buffer_[src.index_ + 1],
+                        &buffer_[dest.index_]);
+    }
+  }
+
+#if DCHECK_IS_ON()
+  // Asserts the given index is dereferenceable. The index is an index into the
+  // buffer, not an index used by operator[] or at() which will be offsets from
+  // begin.
+  void CheckValidIndex(size_t i) const {
+    if (begin_ <= end_)
+      DCHECK(i >= begin_ && i < end_);
+    else
+      DCHECK((i >= begin_ && i < buffer_.capacity()) || i < end_);
+  }
+
+  // Asserts the given index is either dereferenceable or points to end().
+  void CheckValidIndexOrEnd(size_t i) const {
+    if (i != end_)
+      CheckValidIndex(i);
+  }
+
+  void ValidateIterator(const const_iterator& i) const {
+    DCHECK(i.parent_deque_ == this);
+    i.CheckUnstableUsage();
+  }
+
+  // See generation_ below.
+  void IncrementGeneration() { generation_++; }
+#else
+  // No-op versions of these functions for release builds.
+  void CheckValidIndex(size_t) const {}
+  void CheckValidIndexOrEnd(size_t) const {}
+  void ValidateIterator(const const_iterator& i) const {}
+  void IncrementGeneration() {}
+#endif
+
+  // Danger, the buffer_.capacity() is the "internal capacity" which is
+  // capacity() + 1 since there is an extra item to indicate the end. Otherwise
+  // being completely empty and completely full are indistinguishable (begin ==
+  // end). We could add a separate flag to avoid it, but that adds significant
+  // extra complexity since every computation will have to check for it. Always
+  // keeping one extra unused element in the buffer makes iterator computations
+  // much simpler.
+  //
+  // Container internal code will want to use buffer_.capacity() for offset
+  // computations rather than capacity().
+  VectorBuffer buffer_;
+  size_type begin_ = 0;
+  size_type end_ = 0;
+
+#if DCHECK_IS_ON()
+  // Incremented every time a modification is made that could affect iterator
+  // invalidations.
+  uint64_t generation_ = 0;
+#endif
+};
+
+// Implementations of base::Erase[If] (see base/stl_util.h).
+template <class T, class Value>
+void Erase(circular_deque<T>& container, const Value& value) {
+  container.erase(std::remove(container.begin(), container.end(), value),
+                  container.end());
+}
+
+template <class T, class Predicate>
+void EraseIf(circular_deque<T>& container, Predicate pred) {
+  container.erase(std::remove_if(container.begin(), container.end(), pred),
+                  container.end());
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_CIRCULAR_DEQUE_H_
diff --git a/src/base/containers/flat_map.h b/src/base/containers/flat_map.h
new file mode 100644 (file)
index 0000000..b4fe519
--- /dev/null
@@ -0,0 +1,362 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_FLAT_MAP_H_
+#define BASE_CONTAINERS_FLAT_MAP_H_
+
+#include <functional>
+#include <tuple>
+#include <utility>
+
+#include "base/containers/flat_tree.h"
+#include "base/logging.h"
+#include "base/template_util.h"
+
+namespace base {
+
+namespace internal {
+
+// An implementation of the flat_tree GetKeyFromValue template parameter that
+// extracts the key as the first element of a pair.
+template <class Key, class Mapped>
+struct GetKeyFromValuePairFirst {
+  const Key& operator()(const std::pair<Key, Mapped>& p) const {
+    return p.first;
+  }
+};
+
+}  // namespace internal
+
+// flat_map is a container with a std::map-like interface that stores its
+// contents in a sorted vector.
+//
+// Please see //base/containers/README.md for an overview of which container
+// to select.
+//
+// PROS
+//
+//  - Good memory locality.
+//  - Low overhead, especially for smaller maps.
+//  - Performance is good for more workloads than you might expect (see
+//    overview link above).
+//  - Supports C++14 map interface.
+//
+// CONS
+//
+//  - Inserts and removals are O(n).
+//
+// IMPORTANT NOTES
+//
+//  - Iterators are invalidated across mutations.
+//  - If possible, construct a flat_map in one operation by inserting into
+//    a std::vector and moving that vector into the flat_map constructor.
+//
+// QUICK REFERENCE
+//
+// Most of the core functionality is inherited from flat_tree. Please see
+// flat_tree.h for more details for most of these functions. As a quick
+// reference, the functions available are:
+//
+// Constructors (inputs need not be sorted):
+//   flat_map(InputIterator first, InputIterator last,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare());
+//   flat_map(const flat_map&);
+//   flat_map(flat_map&&);
+//   flat_map(std::vector<value_type>,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare()); // Re-use storage.
+//   flat_map(std::initializer_list<value_type> ilist,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& comp = Compare());
+//
+// Assignment functions:
+//   flat_map& operator=(const flat_map&);
+//   flat_map& operator=(flat_map&&);
+//   flat_map& operator=(initializer_list<value_type>);
+//
+// Memory management functions:
+//   void   reserve(size_t);
+//   size_t capacity() const;
+//   void   shrink_to_fit();
+//
+// Size management functions:
+//   void   clear();
+//   size_t size() const;
+//   size_t max_size() const;
+//   bool   empty() const;
+//
+// Iterator functions:
+//   iterator               begin();
+//   const_iterator         begin() const;
+//   const_iterator         cbegin() const;
+//   iterator               end();
+//   const_iterator         end() const;
+//   const_iterator         cend() const;
+//   reverse_iterator       rbegin();
+//   const reverse_iterator rbegin() const;
+//   const_reverse_iterator crbegin() const;
+//   reverse_iterator       rend();
+//   const_reverse_iterator rend() const;
+//   const_reverse_iterator crend() const;
+//
+// Insert and accessor functions:
+//   mapped_type&         operator[](const key_type&);
+//   mapped_type&         operator[](key_type&&);
+//   pair<iterator, bool> insert(const value_type&);
+//   pair<iterator, bool> insert(value_type&&);
+//   iterator             insert(const_iterator hint, const value_type&);
+//   iterator             insert(const_iterator hint, value_type&&);
+//   void                 insert(InputIterator first, InputIterator last,
+//                               FlatContainerDupes = KEEP_FIRST_OF_DUPES);
+//   pair<iterator, bool> insert_or_assign(K&&, M&&);
+//   iterator             insert_or_assign(const_iterator hint, K&&, M&&);
+//   pair<iterator, bool> emplace(Args&&...);
+//   iterator             emplace_hint(const_iterator, Args&&...);
+//   pair<iterator, bool> try_emplace(K&&, Args&&...);
+//   iterator             try_emplace(const_iterator hint, K&&, Args&&...);
+//
+// Erase functions:
+//   iterator erase(iterator);
+//   iterator erase(const_iterator);
+//   iterator erase(const_iterator first, const_iterator& last);
+//   template <class K> size_t erase(const K& key);
+//
+// Comparators (see std::map documentation).
+//   key_compare   key_comp() const;
+//   value_compare value_comp() const;
+//
+// Search functions:
+//   template <typename K> size_t                   count(const K&) const;
+//   template <typename K> iterator                 find(const K&);
+//   template <typename K> const_iterator           find(const K&) const;
+//   template <typename K> pair<iterator, iterator> equal_range(const K&);
+//   template <typename K> iterator                 lower_bound(const K&);
+//   template <typename K> const_iterator           lower_bound(const K&) const;
+//   template <typename K> iterator                 upper_bound(const K&);
+//   template <typename K> const_iterator           upper_bound(const K&) const;
+//
+// General functions:
+//   void swap(flat_map&&);
+//
+// Non-member operators:
+//   bool operator==(const flat_map&, const flat_map);
+//   bool operator!=(const flat_map&, const flat_map);
+//   bool operator<(const flat_map&, const flat_map);
+//   bool operator>(const flat_map&, const flat_map);
+//   bool operator>=(const flat_map&, const flat_map);
+//   bool operator<=(const flat_map&, const flat_map);
+//
+template <class Key, class Mapped, class Compare = std::less<>>
+class flat_map : public ::base::internal::flat_tree<
+                     Key,
+                     std::pair<Key, Mapped>,
+                     ::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
+                     Compare> {
+ private:
+  using tree = typename ::base::internal::flat_tree<
+      Key,
+      std::pair<Key, Mapped>,
+      ::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
+      Compare>;
+
+ public:
+  using key_type = typename tree::key_type;
+  using mapped_type = Mapped;
+  using value_type = typename tree::value_type;
+  using iterator = typename tree::iterator;
+  using const_iterator = typename tree::const_iterator;
+
+  // --------------------------------------------------------------------------
+  // Lifetime and assignments.
+  //
+  // Note: we could do away with these constructors, destructor and assignment
+  // operator overloads by inheriting |tree|'s, but this breaks the GCC build
+  // due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84782 (see
+  // https://crbug.com/837221).
+
+  flat_map() = default;
+  explicit flat_map(const Compare& comp);
+
+  template <class InputIterator>
+  flat_map(InputIterator first,
+           InputIterator last,
+           FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+           const Compare& comp = Compare());
+
+  flat_map(const flat_map&) = default;
+  flat_map(flat_map&&) noexcept = default;
+
+  flat_map(std::vector<value_type> items,
+           FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+           const Compare& comp = Compare());
+
+  flat_map(std::initializer_list<value_type> ilist,
+           FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+           const Compare& comp = Compare());
+
+  ~flat_map() = default;
+
+  flat_map& operator=(const flat_map&) = default;
+  flat_map& operator=(flat_map&&) = default;
+  // Takes the first if there are duplicates in the initializer list.
+  flat_map& operator=(std::initializer_list<value_type> ilist);
+
+  // --------------------------------------------------------------------------
+  // Map-specific insert operations.
+  //
+  // Normal insert() functions are inherited from flat_tree.
+  //
+  // Assume that every operation invalidates iterators and references.
+  // Insertion of one element can take O(size).
+
+  mapped_type& operator[](const key_type& key);
+  mapped_type& operator[](key_type&& key);
+
+  template <class K, class M>
+  std::pair<iterator, bool> insert_or_assign(K&& key, M&& obj);
+  template <class K, class M>
+  iterator insert_or_assign(const_iterator hint, K&& key, M&& obj);
+
+  template <class K, class... Args>
+  std::enable_if_t<std::is_constructible<key_type, K&&>::value,
+                   std::pair<iterator, bool>>
+  try_emplace(K&& key, Args&&... args);
+
+  template <class K, class... Args>
+  std::enable_if_t<std::is_constructible<key_type, K&&>::value, iterator>
+  try_emplace(const_iterator hint, K&& key, Args&&... args);
+
+  // --------------------------------------------------------------------------
+  // General operations.
+  //
+  // Assume that swap invalidates iterators and references.
+
+  void swap(flat_map& other) noexcept;
+
+  friend void swap(flat_map& lhs, flat_map& rhs) noexcept { lhs.swap(rhs); }
+};
+
+// ----------------------------------------------------------------------------
+// Lifetime.
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(const Compare& comp) : tree(comp) {}
+
+template <class Key, class Mapped, class Compare>
+template <class InputIterator>
+flat_map<Key, Mapped, Compare>::flat_map(InputIterator first,
+                                         InputIterator last,
+                                         FlatContainerDupes dupe_handling,
+                                         const Compare& comp)
+    : tree(first, last, dupe_handling, comp) {}
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(std::vector<value_type> items,
+                                         FlatContainerDupes dupe_handling,
+                                         const Compare& comp)
+    : tree(std::move(items), dupe_handling, comp) {}
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(
+    std::initializer_list<value_type> ilist,
+    FlatContainerDupes dupe_handling,
+    const Compare& comp)
+    : flat_map(std::begin(ilist), std::end(ilist), dupe_handling, comp) {}
+
+// ----------------------------------------------------------------------------
+// Assignments.
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator=(
+    std::initializer_list<value_type> ilist) -> flat_map& {
+  // When https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84782 gets fixed, we
+  // need to remember to inherit tree::operator= to prevent
+  //   flat_map<...> x;
+  //   x = {...};
+  // from first creating a flat_map and then move assigning it. This most
+  // likely would be optimized away but still affects our debug builds.
+  tree::operator=(ilist);
+  return *this;
+}
+
+// ----------------------------------------------------------------------------
+// Insert operations.
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator[](const key_type& key)
+    -> mapped_type& {
+  iterator found = tree::lower_bound(key);
+  if (found == tree::end() || tree::key_comp()(key, found->first))
+    found = tree::unsafe_emplace(found, key, mapped_type());
+  return found->second;
+}
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator[](key_type&& key)
+    -> mapped_type& {
+  iterator found = tree::lower_bound(key);
+  if (found == tree::end() || tree::key_comp()(key, found->first))
+    found = tree::unsafe_emplace(found, std::move(key), mapped_type());
+  return found->second;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class M>
+auto flat_map<Key, Mapped, Compare>::insert_or_assign(K&& key, M&& obj)
+    -> std::pair<iterator, bool> {
+  auto result =
+      tree::emplace_key_args(key, std::forward<K>(key), std::forward<M>(obj));
+  if (!result.second)
+    result.first->second = std::forward<M>(obj);
+  return result;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class M>
+auto flat_map<Key, Mapped, Compare>::insert_or_assign(const_iterator hint,
+                                                      K&& key,
+                                                      M&& obj) -> iterator {
+  auto result = tree::emplace_hint_key_args(hint, key, std::forward<K>(key),
+                                            std::forward<M>(obj));
+  if (!result.second)
+    result.first->second = std::forward<M>(obj);
+  return result.first;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class... Args>
+auto flat_map<Key, Mapped, Compare>::try_emplace(K&& key, Args&&... args)
+    -> std::enable_if_t<std::is_constructible<key_type, K&&>::value,
+                        std::pair<iterator, bool>> {
+  return tree::emplace_key_args(
+      key, std::piecewise_construct,
+      std::forward_as_tuple(std::forward<K>(key)),
+      std::forward_as_tuple(std::forward<Args>(args)...));
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class... Args>
+auto flat_map<Key, Mapped, Compare>::try_emplace(const_iterator hint,
+                                                 K&& key,
+                                                 Args&&... args)
+    -> std::enable_if_t<std::is_constructible<key_type, K&&>::value, iterator> {
+  return tree::emplace_hint_key_args(
+             hint, key, std::piecewise_construct,
+             std::forward_as_tuple(std::forward<K>(key)),
+             std::forward_as_tuple(std::forward<Args>(args)...))
+      .first;
+}
+
+// ----------------------------------------------------------------------------
+// General operations.
+
+template <class Key, class Mapped, class Compare>
+void flat_map<Key, Mapped, Compare>::swap(flat_map& other) noexcept {
+  tree::swap(other);
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_FLAT_MAP_H_
diff --git a/src/base/containers/flat_set.h b/src/base/containers/flat_set.h
new file mode 100644 (file)
index 0000000..700617f
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_FLAT_SET_H_
+#define BASE_CONTAINERS_FLAT_SET_H_
+
+#include <functional>
+
+#include "base/containers/flat_tree.h"
+#include "base/template_util.h"
+
+namespace base {
+
+// flat_set is a container with a std::set-like interface that stores its
+// contents in a sorted vector.
+//
+// Please see //base/containers/README.md for an overview of which container
+// to select.
+//
+// PROS
+//
+//  - Good memory locality.
+//  - Low overhead, especially for smaller sets.
+//  - Performance is good for more workloads than you might expect (see
+//    overview link above).
+//  - Supports C++14 set interface.
+//
+// CONS
+//
+//  - Inserts and removals are O(n).
+//
+// IMPORTANT NOTES
+//
+//  - Iterators are invalidated across mutations.
+//  - If possible, construct a flat_set in one operation by inserting into
+//    a std::vector and moving that vector into the flat_set constructor.
+//  - For multiple removals use base::EraseIf() which is O(n) rather than
+//    O(n * removed_items).
+//
+// QUICK REFERENCE
+//
+// Most of the core functionality is inherited from flat_tree. Please see
+// flat_tree.h for more details for most of these functions. As a quick
+// reference, the functions available are:
+//
+// Constructors (inputs need not be sorted):
+//   flat_set(InputIterator first, InputIterator last,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare());
+//   flat_set(const flat_set&);
+//   flat_set(flat_set&&);
+//   flat_set(std::vector<Key>,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& compare = Compare());  // Re-use storage.
+//   flat_set(std::initializer_list<value_type> ilist,
+//            FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+//            const Compare& comp = Compare());
+//
+// Assignment functions:
+//   flat_set& operator=(const flat_set&);
+//   flat_set& operator=(flat_set&&);
+//   flat_set& operator=(initializer_list<Key>);
+//
+// Memory management functions:
+//   void   reserve(size_t);
+//   size_t capacity() const;
+//   void   shrink_to_fit();
+//
+// Size management functions:
+//   void   clear();
+//   size_t size() const;
+//   size_t max_size() const;
+//   bool   empty() const;
+//
+// Iterator functions:
+//   iterator               begin();
+//   const_iterator         begin() const;
+//   const_iterator         cbegin() const;
+//   iterator               end();
+//   const_iterator         end() const;
+//   const_iterator         cend() const;
+//   reverse_iterator       rbegin();
+//   const reverse_iterator rbegin() const;
+//   const_reverse_iterator crbegin() const;
+//   reverse_iterator       rend();
+//   const_reverse_iterator rend() const;
+//   const_reverse_iterator crend() const;
+//
+// Insert and accessor functions:
+//   pair<iterator, bool> insert(const key_type&);
+//   pair<iterator, bool> insert(key_type&&);
+//   void                 insert(InputIterator first, InputIterator last,
+//                               FlatContainerDupes = KEEP_FIRST_OF_DUPES);
+//   iterator             insert(const_iterator hint, const key_type&);
+//   iterator             insert(const_iterator hint, key_type&&);
+//   pair<iterator, bool> emplace(Args&&...);
+//   iterator             emplace_hint(const_iterator, Args&&...);
+//
+// Erase functions:
+//   iterator erase(iterator);
+//   iterator erase(const_iterator);
+//   iterator erase(const_iterator first, const_iterator& last);
+//   template <typename K> size_t erase(const K& key);
+//
+// Comparators (see std::set documentation).
+//   key_compare   key_comp() const;
+//   value_compare value_comp() const;
+//
+// Search functions:
+//   template <typename K> size_t                   count(const K&) const;
+//   template <typename K> iterator                 find(const K&);
+//   template <typename K> const_iterator           find(const K&) const;
+//   template <typename K> bool                     contains(const K&) const;
+//   template <typename K> pair<iterator, iterator> equal_range(K&);
+//   template <typename K> iterator                 lower_bound(const K&);
+//   template <typename K> const_iterator           lower_bound(const K&) const;
+//   template <typename K> iterator                 upper_bound(const K&);
+//   template <typename K> const_iterator           upper_bound(const K&) const;
+//
+// General functions:
+//   void swap(flat_set&&);
+//
+// Non-member operators:
+//   bool operator==(const flat_set&, const flat_set);
+//   bool operator!=(const flat_set&, const flat_set);
+//   bool operator<(const flat_set&, const flat_set);
+//   bool operator>(const flat_set&, const flat_set);
+//   bool operator>=(const flat_set&, const flat_set);
+//   bool operator<=(const flat_set&, const flat_set);
+//
+template <class Key, class Compare = std::less<>>
+using flat_set = typename ::base::internal::flat_tree<
+    Key,
+    Key,
+    ::base::internal::GetKeyFromValueIdentity<Key>,
+    Compare>;
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_FLAT_SET_H_
\ No newline at end of file
diff --git a/src/base/containers/flat_tree.h b/src/base/containers/flat_tree.h
new file mode 100644 (file)
index 0000000..65afe58
--- /dev/null
@@ -0,0 +1,1004 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_FLAT_TREE_H_
+#define BASE_CONTAINERS_FLAT_TREE_H_
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <vector>
+
+#include "base/template_util.h"
+
+namespace base {
+
+enum FlatContainerDupes {
+  KEEP_FIRST_OF_DUPES,
+  KEEP_LAST_OF_DUPES,
+};
+
+namespace internal {
+
+// This is a convenience method returning true if Iterator is at least a
+// ForwardIterator and thus supports multiple passes over a range.
+template <class Iterator>
+constexpr bool is_multipass() {
+  return std::is_base_of<
+      std::forward_iterator_tag,
+      typename std::iterator_traits<Iterator>::iterator_category>::value;
+}
+
+// This algorithm is like unique() from the standard library except it
+// selects only the last of consecutive values instead of the first.
+template <class Iterator, class BinaryPredicate>
+Iterator LastUnique(Iterator first, Iterator last, BinaryPredicate compare) {
+  Iterator replacable = std::adjacent_find(first, last, compare);
+
+  // No duplicate elements found.
+  if (replacable == last)
+    return last;
+
+  first = std::next(replacable);
+
+  // Last element is a duplicate but all others are unique.
+  if (first == last)
+    return replacable;
+
+  // This loop is based on std::adjacent_find but std::adjacent_find doesn't
+  // quite cut it.
+  for (Iterator next = std::next(first); next != last; ++next, ++first) {
+    if (!compare(*first, *next))
+      *replacable++ = std::move(*first);
+  }
+
+  // Last element should be copied unconditionally.
+  *replacable++ = std::move(*first);
+  return replacable;
+}
+
+// Uses SFINAE to detect whether type has is_transparent member.
+template <typename T, typename = void>
+struct IsTransparentCompare : std::false_type {};
+template <typename T>
+struct IsTransparentCompare<T, std::void_t<typename T::is_transparent>>
+    : std::true_type {};
+
+// Implementation -------------------------------------------------------------
+
+// Implementation of a sorted vector for backing flat_set and flat_map. Do not
+// use directly.
+//
+// The use of "value" in this is like std::map uses, meaning it's the thing
+// contained (in the case of map it's a <Kay, Mapped> pair). The Key is how
+// things are looked up. In the case of a set, Key == Value. In the case of
+// a map, the Key is a component of a Value.
+//
+// The helper class GetKeyFromValue provides the means to extract a key from a
+// value for comparison purposes. It should implement:
+//   const Key& operator()(const Value&).
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+class flat_tree {
+ private:
+  using underlying_type = std::vector<Value>;
+
+ public:
+  // --------------------------------------------------------------------------
+  // Types.
+  //
+  using key_type = Key;
+  using key_compare = KeyCompare;
+  using value_type = Value;
+
+  // Wraps the templated key comparison to compare values.
+  class value_compare : public key_compare {
+   public:
+    value_compare() = default;
+
+    template <class Cmp>
+    explicit value_compare(Cmp&& compare_arg)
+        : KeyCompare(std::forward<Cmp>(compare_arg)) {}
+
+    bool operator()(const value_type& left, const value_type& right) const {
+      GetKeyFromValue extractor;
+      return key_compare::operator()(extractor(left), extractor(right));
+    }
+  };
+
+  using pointer = typename underlying_type::pointer;
+  using const_pointer = typename underlying_type::const_pointer;
+  using reference = typename underlying_type::reference;
+  using const_reference = typename underlying_type::const_reference;
+  using size_type = typename underlying_type::size_type;
+  using difference_type = typename underlying_type::difference_type;
+  using iterator = typename underlying_type::iterator;
+  using const_iterator = typename underlying_type::const_iterator;
+  using reverse_iterator = typename underlying_type::reverse_iterator;
+  using const_reverse_iterator =
+      typename underlying_type::const_reverse_iterator;
+
+  // --------------------------------------------------------------------------
+  // Lifetime.
+  //
+  // Constructors that take range guarantee O(N * log^2(N)) + O(N) complexity
+  // and take O(N * log(N)) + O(N) if extra memory is available (N is a range
+  // length).
+  //
+  // Assume that move constructors invalidate iterators and references.
+  //
+  // The constructors that take ranges, lists, and vectors do not require that
+  // the input be sorted.
+
+  flat_tree();
+  explicit flat_tree(const key_compare& comp);
+
+  template <class InputIterator>
+  flat_tree(InputIterator first,
+            InputIterator last,
+            FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+            const key_compare& comp = key_compare());
+
+  flat_tree(const flat_tree&);
+  flat_tree(flat_tree&&) noexcept = default;
+
+  flat_tree(std::vector<value_type> items,
+            FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+            const key_compare& comp = key_compare());
+
+  flat_tree(std::initializer_list<value_type> ilist,
+            FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+            const key_compare& comp = key_compare());
+
+  ~flat_tree();
+
+  // --------------------------------------------------------------------------
+  // Assignments.
+  //
+  // Assume that move assignment invalidates iterators and references.
+
+  flat_tree& operator=(const flat_tree&);
+  flat_tree& operator=(flat_tree&&);
+  // Takes the first if there are duplicates in the initializer list.
+  flat_tree& operator=(std::initializer_list<value_type> ilist);
+
+  // --------------------------------------------------------------------------
+  // Memory management.
+  //
+  // Beware that shrink_to_fit() simply forwards the request to the
+  // underlying_type and its implementation is free to optimize otherwise and
+  // leave capacity() to be greater that its size.
+  //
+  // reserve() and shrink_to_fit() invalidate iterators and references.
+
+  void reserve(size_type new_capacity);
+  size_type capacity() const;
+  void shrink_to_fit();
+
+  // --------------------------------------------------------------------------
+  // Size management.
+  //
+  // clear() leaves the capacity() of the flat_tree unchanged.
+
+  void clear();
+
+  size_type size() const;
+  size_type max_size() const;
+  bool empty() const;
+
+  // --------------------------------------------------------------------------
+  // Iterators.
+
+  iterator begin();
+  const_iterator begin() const;
+  const_iterator cbegin() const;
+
+  iterator end();
+  const_iterator end() const;
+  const_iterator cend() const;
+
+  reverse_iterator rbegin();
+  const_reverse_iterator rbegin() const;
+  const_reverse_iterator crbegin() const;
+
+  reverse_iterator rend();
+  const_reverse_iterator rend() const;
+  const_reverse_iterator crend() const;
+
+  // --------------------------------------------------------------------------
+  // Insert operations.
+  //
+  // Assume that every operation invalidates iterators and references.
+  // Insertion of one element can take O(size). Capacity of flat_tree grows in
+  // an implementation-defined manner.
+  //
+  // NOTE: Prefer to build a new flat_tree from a std::vector (or similar)
+  // instead of calling insert() repeatedly.
+
+  std::pair<iterator, bool> insert(const value_type& val);
+  std::pair<iterator, bool> insert(value_type&& val);
+
+  iterator insert(const_iterator position_hint, const value_type& x);
+  iterator insert(const_iterator position_hint, value_type&& x);
+
+  // This method inserts the values from the range [first, last) into the
+  // current tree. In case of KEEP_LAST_OF_DUPES newly added elements can
+  // overwrite existing values.
+  template <class InputIterator>
+  void insert(InputIterator first,
+              InputIterator last,
+              FlatContainerDupes dupes = KEEP_FIRST_OF_DUPES);
+
+  template <class... Args>
+  std::pair<iterator, bool> emplace(Args&&... args);
+
+  template <class... Args>
+  iterator emplace_hint(const_iterator position_hint, Args&&... args);
+
+  // --------------------------------------------------------------------------
+  // Erase operations.
+  //
+  // Assume that every operation invalidates iterators and references.
+  //
+  // erase(position), erase(first, last) can take O(size).
+  // erase(key) may take O(size) + O(log(size)).
+  //
+  // Prefer base::EraseIf() or some other variation on erase(remove(), end())
+  // idiom when deleting multiple non-consecutive elements.
+
+  iterator erase(iterator position);
+  iterator erase(const_iterator position);
+  iterator erase(const_iterator first, const_iterator last);
+  template <typename K>
+  size_type erase(const K& key);
+
+  // --------------------------------------------------------------------------
+  // Comparators.
+
+  key_compare key_comp() const;
+  value_compare value_comp() const;
+
+  // --------------------------------------------------------------------------
+  // Search operations.
+  //
+  // Search operations have O(log(size)) complexity.
+
+  template <typename K>
+  size_type count(const K& key) const;
+
+  template <typename K>
+  iterator find(const K& key);
+
+  template <typename K>
+  const_iterator find(const K& key) const;
+
+  template <typename K>
+  std::pair<iterator, iterator> equal_range(const K& key);
+
+  template <typename K>
+  std::pair<const_iterator, const_iterator> equal_range(const K& key) const;
+
+  template <typename K>
+  iterator lower_bound(const K& key);
+
+  template <typename K>
+  const_iterator lower_bound(const K& key) const;
+
+  template <typename K>
+  iterator upper_bound(const K& key);
+
+  template <typename K>
+  const_iterator upper_bound(const K& key) const;
+
+  // --------------------------------------------------------------------------
+  // General operations.
+  //
+  // Assume that swap invalidates iterators and references.
+  //
+  // Implementation note: currently we use operator==() and operator<() on
+  // std::vector, because they have the same contract we need, so we use them
+  // directly for brevity and in case it is more optimal than calling equal()
+  // and lexicograhpical_compare(). If the underlying container type is changed,
+  // this code may need to be modified.
+
+  void swap(flat_tree& other) noexcept;
+
+  friend bool operator==(const flat_tree& lhs, const flat_tree& rhs) {
+    return lhs.impl_.body_ == rhs.impl_.body_;
+  }
+
+  friend bool operator!=(const flat_tree& lhs, const flat_tree& rhs) {
+    return !(lhs == rhs);
+  }
+
+  friend bool operator<(const flat_tree& lhs, const flat_tree& rhs) {
+    return lhs.impl_.body_ < rhs.impl_.body_;
+  }
+
+  friend bool operator>(const flat_tree& lhs, const flat_tree& rhs) {
+    return rhs < lhs;
+  }
+
+  friend bool operator>=(const flat_tree& lhs, const flat_tree& rhs) {
+    return !(lhs < rhs);
+  }
+
+  friend bool operator<=(const flat_tree& lhs, const flat_tree& rhs) {
+    return !(lhs > rhs);
+  }
+
+  friend void swap(flat_tree& lhs, flat_tree& rhs) noexcept { lhs.swap(rhs); }
+
+ protected:
+  // Emplaces a new item into the tree that is known not to be in it. This
+  // is for implementing map operator[].
+  template <class... Args>
+  iterator unsafe_emplace(const_iterator position, Args&&... args);
+
+  // Attempts to emplace a new element with key |key|. Only if |key| is not yet
+  // present, construct value_type from |args| and insert it. Returns an
+  // iterator to the element with key |key| and a bool indicating whether an
+  // insertion happened.
+  template <class K, class... Args>
+  std::pair<iterator, bool> emplace_key_args(const K& key, Args&&... args);
+
+  // Similar to |emplace_key_args|, but checks |hint| first as a possible
+  // insertion position.
+  template <class K, class... Args>
+  std::pair<iterator, bool> emplace_hint_key_args(const_iterator hint,
+                                                  const K& key,
+                                                  Args&&... args);
+
+ private:
+  // Helper class for e.g. lower_bound that can compare a value on the left
+  // to a key on the right.
+  struct KeyValueCompare {
+    // The key comparison object must outlive this class.
+    explicit KeyValueCompare(const key_compare& key_comp)
+        : key_comp_(key_comp) {}
+
+    template <typename T, typename U>
+    bool operator()(const T& lhs, const U& rhs) const {
+      return key_comp_(extract_if_value_type(lhs), extract_if_value_type(rhs));
+    }
+
+   private:
+    const key_type& extract_if_value_type(const value_type& v) const {
+      GetKeyFromValue extractor;
+      return extractor(v);
+    }
+
+    template <typename K>
+    const K& extract_if_value_type(const K& k) const {
+      return k;
+    }
+
+    const key_compare& key_comp_;
+  };
+
+  const flat_tree& as_const() { return *this; }
+
+  iterator const_cast_it(const_iterator c_it) {
+    auto distance = std::distance(cbegin(), c_it);
+    return std::next(begin(), distance);
+  }
+
+  // This method is inspired by both std::map::insert(P&&) and
+  // std::map::insert_or_assign(const K&, V&&). It inserts val if an equivalent
+  // element is not present yet, otherwise it overwrites. It returns an iterator
+  // to the modified element and a flag indicating whether insertion or
+  // assignment happened.
+  template <class V>
+  std::pair<iterator, bool> insert_or_assign(V&& val) {
+    auto position = lower_bound(GetKeyFromValue()(val));
+
+    if (position == end() || value_comp()(val, *position))
+      return {impl_.body_.emplace(position, std::forward<V>(val)), true};
+
+    *position = std::forward<V>(val);
+    return {position, false};
+  }
+
+  // This method is similar to insert_or_assign, with the following differences:
+  // - Instead of searching [begin(), end()) it only searches [first, last).
+  // - In case no equivalent element is found, val is appended to the end of the
+  //   underlying body and an iterator to the next bigger element in [first,
+  //   last) is returned.
+  template <class V>
+  std::pair<iterator, bool> append_or_assign(iterator first,
+                                             iterator last,
+                                             V&& val) {
+    auto position = std::lower_bound(first, last, val, value_comp());
+
+    if (position == last || value_comp()(val, *position)) {
+      // emplace_back might invalidate position, which is why distance needs to
+      // be cached.
+      const difference_type distance = std::distance(begin(), position);
+      impl_.body_.emplace_back(std::forward<V>(val));
+      return {std::next(begin(), distance), true};
+    }
+
+    *position = std::forward<V>(val);
+    return {position, false};
+  }
+
+  // This method is similar to insert, with the following differences:
+  // - Instead of searching [begin(), end()) it only searches [first, last).
+  // - In case no equivalent element is found, val is appended to the end of the
+  //   underlying body and an iterator to the next bigger element in [first,
+  //   last) is returned.
+  template <class V>
+  std::pair<iterator, bool> append_unique(iterator first,
+                                          iterator last,
+                                          V&& val) {
+    auto position = std::lower_bound(first, last, val, value_comp());
+
+    if (position == last || value_comp()(val, *position)) {
+      // emplace_back might invalidate position, which is why distance needs to
+      // be cached.
+      const difference_type distance = std::distance(begin(), position);
+      impl_.body_.emplace_back(std::forward<V>(val));
+      return {std::next(begin(), distance), true};
+    }
+
+    return {position, false};
+  }
+
+  void sort_and_unique(iterator first,
+                       iterator last,
+                       FlatContainerDupes dupes) {
+    // Preserve stability for the unique code below.
+    std::stable_sort(first, last, impl_.get_value_comp());
+
+    auto comparator = [this](const value_type& lhs, const value_type& rhs) {
+      // lhs is already <= rhs due to sort, therefore
+      // !(lhs < rhs) <=> lhs == rhs.
+      return !impl_.get_value_comp()(lhs, rhs);
+    };
+
+    iterator erase_after;
+    switch (dupes) {
+      case KEEP_FIRST_OF_DUPES:
+        erase_after = std::unique(first, last, comparator);
+        break;
+      case KEEP_LAST_OF_DUPES:
+        erase_after = LastUnique(first, last, comparator);
+        break;
+    }
+    erase(erase_after, last);
+  }
+
+  // To support comparators that may not be possible to default-construct, we
+  // have to store an instance of Compare. Using this to store all internal
+  // state of flat_tree and using private inheritance to store compare lets us
+  // take advantage of an empty base class optimization to avoid extra space in
+  // the common case when Compare has no state.
+  struct Impl : private value_compare {
+    Impl() = default;
+
+    template <class Cmp, class... Body>
+    explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
+        : value_compare(std::forward<Cmp>(compare_arg)),
+          body_(std::forward<Body>(underlying_type_args)...) {}
+
+    const value_compare& get_value_comp() const { return *this; }
+    const key_compare& get_key_comp() const { return *this; }
+
+    underlying_type body_;
+  } impl_;
+
+  // If the compare is not transparent we want to construct key_type once.
+  template <typename K>
+  using KeyTypeOrK = typename std::
+      conditional<IsTransparentCompare<key_compare>::value, K, key_type>::type;
+};
+
+// ----------------------------------------------------------------------------
+// Lifetime.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree() = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+    const KeyCompare& comp)
+    : impl_(comp) {}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class InputIterator>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+    InputIterator first,
+    InputIterator last,
+    FlatContainerDupes dupe_handling,
+    const KeyCompare& comp)
+    : impl_(comp, first, last) {
+  sort_and_unique(begin(), end(), dupe_handling);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+    const flat_tree&) = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+    std::vector<value_type> items,
+    FlatContainerDupes dupe_handling,
+    const KeyCompare& comp)
+    : impl_(comp, std::move(items)) {
+  sort_and_unique(begin(), end(), dupe_handling);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+    std::initializer_list<value_type> ilist,
+    FlatContainerDupes dupe_handling,
+    const KeyCompare& comp)
+    : flat_tree(std::begin(ilist), std::end(ilist), dupe_handling, comp) {}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::~flat_tree() = default;
+
+// ----------------------------------------------------------------------------
+// Assignments.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(
+    const flat_tree&) -> flat_tree& = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(flat_tree &&)
+    -> flat_tree& = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(
+    std::initializer_list<value_type> ilist) -> flat_tree& {
+  impl_.body_ = ilist;
+  sort_and_unique(begin(), end(), KEEP_FIRST_OF_DUPES);
+  return *this;
+}
+
+// ----------------------------------------------------------------------------
+// Memory management.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::reserve(
+    size_type new_capacity) {
+  impl_.body_.reserve(new_capacity);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::capacity() const
+    -> size_type {
+  return impl_.body_.capacity();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::shrink_to_fit() {
+  impl_.body_.shrink_to_fit();
+}
+
+// ----------------------------------------------------------------------------
+// Size management.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::clear() {
+  impl_.body_.clear();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::size() const
+    -> size_type {
+  return impl_.body_.size();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::max_size() const
+    -> size_type {
+  return impl_.body_.max_size();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+bool flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::empty() const {
+  return impl_.body_.empty();
+}
+
+// ----------------------------------------------------------------------------
+// Iterators.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::begin() -> iterator {
+  return impl_.body_.begin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::begin() const
+    -> const_iterator {
+  return impl_.body_.begin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::cbegin() const
+    -> const_iterator {
+  return impl_.body_.cbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::end() -> iterator {
+  return impl_.body_.end();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::end() const
+    -> const_iterator {
+  return impl_.body_.end();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::cend() const
+    -> const_iterator {
+  return impl_.body_.cend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rbegin()
+    -> reverse_iterator {
+  return impl_.body_.rbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rbegin() const
+    -> const_reverse_iterator {
+  return impl_.body_.rbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::crbegin() const
+    -> const_reverse_iterator {
+  return impl_.body_.crbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rend()
+    -> reverse_iterator {
+  return impl_.body_.rend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rend() const
+    -> const_reverse_iterator {
+  return impl_.body_.rend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::crend() const
+    -> const_reverse_iterator {
+  return impl_.body_.crend();
+}
+
+// ----------------------------------------------------------------------------
+// Insert operations.
+//
+// Currently we use position_hint the same way as eastl or boost:
+// https://github.com/electronicarts/EASTL/blob/master/include/EASTL/vector_set.h#L493
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+    const value_type& val) -> std::pair<iterator, bool> {
+  return emplace_key_args(GetKeyFromValue()(val), val);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+    value_type&& val) -> std::pair<iterator, bool> {
+  return emplace_key_args(GetKeyFromValue()(val), std::move(val));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+    const_iterator position_hint,
+    const value_type& val) -> iterator {
+  return emplace_hint_key_args(position_hint, GetKeyFromValue()(val), val)
+      .first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+    const_iterator position_hint,
+    value_type&& val) -> iterator {
+  return emplace_hint_key_args(position_hint, GetKeyFromValue()(val),
+                               std::move(val))
+      .first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class InputIterator>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+    InputIterator first,
+    InputIterator last,
+    FlatContainerDupes dupes) {
+  if (first == last)
+    return;
+
+  // Cache results whether existing elements should be overwritten and whether
+  // inserting new elements happens immediately or will be done in a batch.
+  const bool overwrite_existing = dupes == KEEP_LAST_OF_DUPES;
+  const bool insert_inplace =
+      is_multipass<InputIterator>() && std::next(first) == last;
+
+  if (insert_inplace) {
+    if (overwrite_existing) {
+      for (; first != last; ++first)
+        insert_or_assign(*first);
+    } else
+      std::copy(first, last, std::inserter(*this, end()));
+    return;
+  }
+
+  // Provide a convenience lambda to obtain an iterator pointing past the last
+  // old element. This needs to be dymanic due to possible re-allocations.
+  const size_type original_size = size();
+  auto middle = [this, original_size]() {
+    return std::next(begin(), original_size);
+  };
+
+  // For batch updates initialize the first insertion point.
+  difference_type pos_first_new = original_size;
+
+  // Loop over the input range while appending new values and overwriting
+  // existing ones, if applicable. Keep track of the first insertion point.
+  if (overwrite_existing) {
+    for (; first != last; ++first) {
+      std::pair<iterator, bool> result =
+          append_or_assign(begin(), middle(), *first);
+      if (result.second) {
+        pos_first_new =
+            std::min(pos_first_new, std::distance(begin(), result.first));
+      }
+    }
+  } else {
+    for (; first != last; ++first) {
+      std::pair<iterator, bool> result =
+          append_unique(begin(), middle(), *first);
+      if (result.second) {
+        pos_first_new =
+            std::min(pos_first_new, std::distance(begin(), result.first));
+      }
+    }
+  }
+
+  // The new elements might be unordered and contain duplicates, so post-process
+  // the just inserted elements and merge them with the rest, inserting them at
+  // the previously found spot.
+  sort_and_unique(middle(), end(), dupes);
+  std::inplace_merge(std::next(begin(), pos_first_new), middle(), end(),
+                     value_comp());
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace(Args&&... args)
+    -> std::pair<iterator, bool> {
+  return insert(value_type(std::forward<Args>(args)...));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_hint(
+    const_iterator position_hint,
+    Args&&... args) -> iterator {
+  return insert(position_hint, value_type(std::forward<Args>(args)...));
+}
+
+// ----------------------------------------------------------------------------
+// Erase operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+    iterator position) -> iterator {
+  return impl_.body_.erase(position);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+    const_iterator position) -> iterator {
+  return impl_.body_.erase(position);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(const K& val)
+    -> size_type {
+  auto eq_range = equal_range(val);
+  auto res = std::distance(eq_range.first, eq_range.second);
+  erase(eq_range.first, eq_range.second);
+  return res;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+    const_iterator first,
+    const_iterator last) -> iterator {
+  return impl_.body_.erase(first, last);
+}
+
+// ----------------------------------------------------------------------------
+// Comparators.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::key_comp() const
+    -> key_compare {
+  return impl_.get_key_comp();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::value_comp() const
+    -> value_compare {
+  return impl_.get_value_comp();
+}
+
+// ----------------------------------------------------------------------------
+// Search operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::count(
+    const K& key) const -> size_type {
+  auto eq_range = equal_range(key);
+  return std::distance(eq_range.first, eq_range.second);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::find(const K& key)
+    -> iterator {
+  return const_cast_it(as_const().find(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::find(
+    const K& key) const -> const_iterator {
+  auto eq_range = equal_range(key);
+  return (eq_range.first == eq_range.second) ? end() : eq_range.first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::equal_range(
+    const K& key) -> std::pair<iterator, iterator> {
+  auto res = as_const().equal_range(key);
+  return {const_cast_it(res.first), const_cast_it(res.second)};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::equal_range(
+    const K& key) const -> std::pair<const_iterator, const_iterator> {
+  auto lower = lower_bound(key);
+
+  GetKeyFromValue extractor;
+  if (lower == end() || impl_.get_key_comp()(key, extractor(*lower)))
+    return {lower, lower};
+
+  return {lower, std::next(lower)};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::lower_bound(
+    const K& key) -> iterator {
+  return const_cast_it(as_const().lower_bound(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::lower_bound(
+    const K& key) const -> const_iterator {
+  static_assert(std::is_convertible<const KeyTypeOrK<K>&, const K&>::value,
+                "Requested type cannot be bound to the container's key_type "
+                "which is required for a non-transparent compare.");
+
+  const KeyTypeOrK<K>& key_ref = key;
+
+  KeyValueCompare key_value(impl_.get_key_comp());
+  return std::lower_bound(begin(), end(), key_ref, key_value);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::upper_bound(
+    const K& key) -> iterator {
+  return const_cast_it(as_const().upper_bound(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::upper_bound(
+    const K& key) const -> const_iterator {
+  static_assert(std::is_convertible<const KeyTypeOrK<K>&, const K&>::value,
+                "Requested type cannot be bound to the container's key_type "
+                "which is required for a non-transparent compare.");
+
+  const KeyTypeOrK<K>& key_ref = key;
+
+  KeyValueCompare key_value(impl_.get_key_comp());
+  return std::upper_bound(begin(), end(), key_ref, key_value);
+}
+
+// ----------------------------------------------------------------------------
+// General operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::swap(
+    flat_tree& other) noexcept {
+  std::swap(impl_, other.impl_);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::unsafe_emplace(
+    const_iterator position,
+    Args&&... args) -> iterator {
+  return impl_.body_.emplace(position, std::forward<Args>(args)...);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class K, class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_key_args(
+    const K& key,
+    Args&&... args) -> std::pair<iterator, bool> {
+  auto lower = lower_bound(key);
+  if (lower == end() || key_comp()(key, GetKeyFromValue()(*lower)))
+    return {unsafe_emplace(lower, std::forward<Args>(args)...), true};
+  return {lower, false};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class K, class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_hint_key_args(
+    const_iterator hint,
+    const K& key,
+    Args&&... args) -> std::pair<iterator, bool> {
+  GetKeyFromValue extractor;
+  if ((hint == begin() || key_comp()(extractor(*std::prev(hint)), key))) {
+    if (hint == end() || key_comp()(key, extractor(*hint))) {
+      // *(hint - 1) < key < *hint => key did not exist and hint is correct.
+      return {unsafe_emplace(hint, std::forward<Args>(args)...), true};
+    }
+    if (!key_comp()(extractor(*hint), key)) {
+      // key == *hint => no-op, return correct hint.
+      return {const_cast_it(hint), false};
+    }
+  }
+  // hint was not helpful, dispatch to hintless version.
+  return emplace_key_args(key, std::forward<Args>(args)...);
+}
+
+// For containers like sets, the key is the same as the value. This implements
+// the GetKeyFromValue template parameter to flat_tree for this case.
+template <class Key>
+struct GetKeyFromValueIdentity {
+  const Key& operator()(const Key& k) const { return k; }
+};
+
+}  // namespace internal
+
+// ----------------------------------------------------------------------------
+// Free functions.
+
+// Erases all elements that match predicate. It has O(size) complexity.
+template <class Key,
+          class Value,
+          class GetKeyFromValue,
+          class KeyCompare,
+          typename Predicate>
+void EraseIf(base::internal::flat_tree<Key, Value, GetKeyFromValue, KeyCompare>&
+                 container,
+             Predicate pred) {
+  container.erase(std::remove_if(container.begin(), container.end(), pred),
+                  container.end());
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_FLAT_TREE_H_
diff --git a/src/base/containers/queue.h b/src/base/containers/queue.h
new file mode 100644 (file)
index 0000000..2d3b480
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_QUEUE_H_
+#define BASE_CONTAINERS_QUEUE_H_
+
+#include <queue>
+
+#include "base/containers/circular_deque.h"
+
+namespace base {
+
+// Provides a definition of base::queue that's like std::queue but uses a
+// base::circular_queue instead of std::deque. Since std::queue is just a
+// wrapper for an underlying type, we can just provide a typedef for it that
+// defaults to the base circular_deque.
+template <class T, class Container = circular_deque<T>>
+using queue = std::queue<T, Container>;
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_QUEUE_H_
diff --git a/src/base/containers/span.h b/src/base/containers/span.h
new file mode 100644 (file)
index 0000000..29c9a97
--- /dev/null
@@ -0,0 +1,453 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_SPAN_H_
+#define BASE_CONTAINERS_SPAN_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace base {
+
+// [views.constants]
+constexpr size_t dynamic_extent = static_cast<size_t>(-1);
+
+template <typename T, size_t Extent = dynamic_extent>
+class span;
+
+namespace internal {
+
+template <typename T>
+struct IsSpanImpl : std::false_type {};
+
+template <typename T, size_t Extent>
+struct IsSpanImpl<span<T, Extent>> : std::true_type {};
+
+template <typename T>
+using IsSpan = IsSpanImpl<std::decay_t<T>>;
+
+template <typename T>
+struct IsStdArrayImpl : std::false_type {};
+
+template <typename T, size_t N>
+struct IsStdArrayImpl<std::array<T, N>> : std::true_type {};
+
+template <typename T>
+using IsStdArray = IsStdArrayImpl<std::decay_t<T>>;
+
+template <typename T>
+using IsCArray = std::is_array<std::remove_reference_t<T>>;
+
+template <typename From, typename To>
+using IsLegalDataConversion = std::is_convertible<From (*)[], To (*)[]>;
+
+template <typename Container, typename T>
+using ContainerHasConvertibleData = IsLegalDataConversion<
+    std::remove_pointer_t<decltype(std::data(std::declval<Container>()))>,
+    T>;
+
+template <typename Container>
+using ContainerHasIntegralSize =
+    std::is_integral<decltype(std::size(std::declval<Container>()))>;
+
+template <typename From, size_t FromExtent, typename To, size_t ToExtent>
+using EnableIfLegalSpanConversion =
+    std::enable_if_t<(ToExtent == dynamic_extent || ToExtent == FromExtent) &&
+                     IsLegalDataConversion<From, To>::value>;
+
+// SFINAE check if Array can be converted to a span<T>.
+template <typename Array, size_t N, typename T, size_t Extent>
+using EnableIfSpanCompatibleArray =
+    std::enable_if_t<(Extent == dynamic_extent || Extent == N) &&
+                     ContainerHasConvertibleData<Array, T>::value>;
+
+// SFINAE check if Container can be converted to a span<T>.
+template <typename Container, typename T>
+using EnableIfSpanCompatibleContainer =
+    std::enable_if_t<!internal::IsSpan<Container>::value &&
+                     !internal::IsStdArray<Container>::value &&
+                     !internal::IsCArray<Container>::value &&
+                     ContainerHasConvertibleData<Container, T>::value &&
+                     ContainerHasIntegralSize<Container>::value>;
+
+}  // namespace internal
+
+// A span is a value type that represents an array of elements of type T. Since
+// it only consists of a pointer to memory with an associated size, it is very
+// light-weight. It is cheap to construct, copy, move and use spans, so that
+// users are encouraged to use it as a pass-by-value parameter. A span does not
+// own the underlying memory, so care must be taken to ensure that a span does
+// not outlive the backing store.
+//
+// span is somewhat analogous to std::string_view, but with arbitrary element
+// types, allowing mutation if T is non-const.
+//
+// span is implicitly convertible from C++ arrays, as well as most [1]
+// container-like types that provide a data() and size() method (such as
+// std::vector<T>). A mutable span<T> can also be implicitly converted to an
+// immutable span<const T>.
+//
+// Consider using a span for functions that take a data pointer and size
+// parameter: it allows the function to still act on an array-like type, while
+// allowing the caller code to be a bit more concise.
+//
+// For read-only data access pass a span<const T>: the caller can supply either
+// a span<const T> or a span<T>, while the callee will have a read-only view.
+// For read-write access a mutable span<T> is required.
+//
+// Without span:
+//   Read-Only:
+//     // std::string HexEncode(const uint8_t* data, size_t size);
+//     std::vector<uint8_t> data_buffer = GenerateData();
+//     std::string r = HexEncode(data_buffer.data(), data_buffer.size());
+//
+//  Mutable:
+//     // ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...);
+//     char str_buffer[100];
+//     SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
+//
+// With span:
+//   Read-Only:
+//     // std::string HexEncode(base::span<const uint8_t> data);
+//     std::vector<uint8_t> data_buffer = GenerateData();
+//     std::string r = HexEncode(data_buffer);
+//
+//  Mutable:
+//     // ssize_t SafeSNPrintf(base::span<char>, const char* fmt, Args...);
+//     char str_buffer[100];
+//     SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
+//
+// Spans with "const" and pointers
+// -------------------------------
+//
+// Const and pointers can get confusing. Here are vectors of pointers and their
+// corresponding spans:
+//
+//   const std::vector<int*>        =>  base::span<int* const>
+//   std::vector<const int*>        =>  base::span<const int*>
+//   const std::vector<const int*>  =>  base::span<const int* const>
+//
+// Differences from the working group proposal
+// -------------------------------------------
+//
+// https://wg21.link/P0122 is the latest working group proposal, Chromium
+// currently implements R7. Differences between the proposal and the
+// implementation are documented in subsections below.
+//
+// Differences from [span.objectrep]:
+// - as_bytes() and as_writable_bytes() return spans of uint8_t instead of
+//   std::byte
+//
+// Differences in constants and types:
+// - index_type is aliased to size_t
+//
+// Differences from [span.sub]:
+// - using size_t instead of ptrdiff_t for indexing
+//
+// Differences from [span.obs]:
+// - using size_t instead of ptrdiff_t to represent size()
+//
+// Differences from [span.elem]:
+// - using size_t instead of ptrdiff_t for indexing
+//
+// Furthermore, all constructors and methods are marked noexcept due to the lack
+// of exceptions in Chromium.
+//
+// Due to the lack of class template argument deduction guides in C++14
+// appropriate make_span() utility functions are provided.
+
+// [span], class template span
+template <typename T, size_t Extent>
+class span {
+ public:
+  using element_type = T;
+  using value_type = std::remove_cv_t<T>;
+  using index_type = size_t;
+  using difference_type = ptrdiff_t;
+  using pointer = T*;
+  using reference = T&;
+  using iterator = T*;
+  using const_iterator = const T*;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  static constexpr index_type extent = Extent;
+
+  // [span.cons], span constructors, copy, assignment, and destructor
+  constexpr span() noexcept : data_(nullptr), size_(0) {
+    static_assert(Extent == dynamic_extent || Extent == 0, "Invalid Extent");
+  }
+
+  constexpr span(T* data, size_t size) noexcept : data_(data), size_(size) {
+    CHECK(Extent == dynamic_extent || Extent == size);
+  }
+
+  // Artificially templatized to break ambiguity for span(ptr, 0).
+  template <typename = void>
+  constexpr span(T* begin, T* end) noexcept : span(begin, end - begin) {
+    // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+    CHECK(begin <= end);
+  }
+
+  template <
+      size_t N,
+      typename = internal::EnableIfSpanCompatibleArray<T (&)[N], N, T, Extent>>
+  constexpr span(T (&array)[N]) noexcept : span(std::data(array), N) {}
+
+  template <
+      size_t N,
+      typename = internal::
+          EnableIfSpanCompatibleArray<std::array<value_type, N>&, N, T, Extent>>
+  constexpr span(std::array<value_type, N>& array) noexcept
+      : span(std::data(array), N) {}
+
+  template <size_t N,
+            typename = internal::EnableIfSpanCompatibleArray<
+                const std::array<value_type, N>&,
+                N,
+                T,
+                Extent>>
+  constexpr span(const std::array<value_type, N>& array) noexcept
+      : span(std::data(array), N) {}
+
+  // Conversion from a container that has compatible std::data() and integral
+  // std::size().
+  template <typename Container,
+            typename = internal::EnableIfSpanCompatibleContainer<Container&, T>>
+  constexpr span(Container& container) noexcept
+      : span(std::data(container), std::size(container)) {}
+
+  template <
+      typename Container,
+      typename = internal::EnableIfSpanCompatibleContainer<const Container&, T>>
+  span(const Container& container) noexcept
+      : span(std::data(container), std::size(container)) {}
+
+  constexpr span(const span& other) noexcept = default;
+
+  // Conversions from spans of compatible types and extents: this allows a
+  // span<T> to be seamlessly used as a span<const T>, but not the other way
+  // around. If extent is not dynamic, OtherExtent has to be equal to Extent.
+  template <
+      typename U,
+      size_t OtherExtent,
+      typename =
+          internal::EnableIfLegalSpanConversion<U, OtherExtent, T, Extent>>
+  constexpr span(const span<U, OtherExtent>& other)
+      : span(other.data(), other.size()) {}
+
+  constexpr span& operator=(const span& other) noexcept = default;
+  ~span() noexcept = default;
+
+  // [span.sub], span subviews
+  template <size_t Count>
+  constexpr span<T, Count> first() const noexcept {
+    static_assert(Extent == dynamic_extent || Count <= Extent,
+                  "Count must not exceed Extent");
+    CHECK(Extent != dynamic_extent || Count <= size());
+    return {data(), Count};
+  }
+
+  template <size_t Count>
+  constexpr span<T, Count> last() const noexcept {
+    static_assert(Extent == dynamic_extent || Count <= Extent,
+                  "Count must not exceed Extent");
+    CHECK(Extent != dynamic_extent || Count <= size());
+    return {data() + (size() - Count), Count};
+  }
+
+  template <size_t Offset, size_t Count = dynamic_extent>
+  constexpr span<T,
+                 (Count != dynamic_extent
+                      ? Count
+                      : (Extent != dynamic_extent ? Extent - Offset
+                                                  : dynamic_extent))>
+  subspan() const noexcept {
+    static_assert(Extent == dynamic_extent || Offset <= Extent,
+                  "Offset must not exceed Extent");
+    static_assert(Extent == dynamic_extent || Count == dynamic_extent ||
+                      Count <= Extent - Offset,
+                  "Count must not exceed Extent - Offset");
+    CHECK(Extent != dynamic_extent || Offset <= size());
+    CHECK(Extent != dynamic_extent || Count == dynamic_extent ||
+          Count <= size() - Offset);
+    return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset};
+  }
+
+  constexpr span<T, dynamic_extent> first(size_t count) const noexcept {
+    // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+    CHECK(count <= size());
+    return {data(), count};
+  }
+
+  constexpr span<T, dynamic_extent> last(size_t count) const noexcept {
+    // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+    CHECK(count <= size());
+    return {data() + (size() - count), count};
+  }
+
+  constexpr span<T, dynamic_extent> subspan(size_t offset,
+                                            size_t count = dynamic_extent) const
+      noexcept {
+    // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+    CHECK(offset <= size());
+    CHECK(count == dynamic_extent || count <= size() - offset);
+    return {data() + offset, count != dynamic_extent ? count : size() - offset};
+  }
+
+  // [span.obs], span observers
+  constexpr size_t size() const noexcept { return size_; }
+  constexpr size_t size_bytes() const noexcept { return size() * sizeof(T); }
+  constexpr bool empty() const noexcept { return size() == 0; }
+
+  // [span.elem], span element access
+  constexpr T& operator[](size_t idx) const noexcept {
+    // Note: CHECK_LT is not constexpr, hence regular CHECK must be used.
+    CHECK(idx < size());
+    return *(data() + idx);
+  }
+
+  constexpr T& operator()(size_t idx) const noexcept {
+    // Note: CHECK_LT is not constexpr, hence regular CHECK must be used.
+    CHECK(idx < size());
+    return *(data() + idx);
+  }
+
+  constexpr T* data() const noexcept { return data_; }
+
+  // [span.iter], span iterator support
+  constexpr iterator begin() const noexcept { return data(); }
+  constexpr iterator end() const noexcept { return data() + size(); }
+
+  constexpr const_iterator cbegin() const noexcept { return begin(); }
+  constexpr const_iterator cend() const noexcept { return end(); }
+
+  constexpr reverse_iterator rbegin() const noexcept {
+    return reverse_iterator(end());
+  }
+  constexpr reverse_iterator rend() const noexcept {
+    return reverse_iterator(begin());
+  }
+
+  constexpr const_reverse_iterator crbegin() const noexcept {
+    return const_reverse_iterator(cend());
+  }
+  constexpr const_reverse_iterator crend() const noexcept {
+    return const_reverse_iterator(cbegin());
+  }
+
+ private:
+  T* data_;
+  size_t size_;
+};
+
+// span<T, Extent>::extent can not be declared inline prior to C++17, hence this
+// definition is required.
+template <class T, size_t Extent>
+constexpr size_t span<T, Extent>::extent;
+
+// [span.comparison], span comparison operators
+// Relational operators. Equality is a element-wise comparison.
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator==(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend());
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator!=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return !(lhs == rhs);
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator<(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return std::lexicographical_compare(lhs.cbegin(), lhs.cend(), rhs.cbegin(),
+                                      rhs.cend());
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator<=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return !(rhs < lhs);
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator>(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return rhs < lhs;
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator>=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+  return !(lhs < rhs);
+}
+
+// [span.objectrep], views of object representation
+template <typename T, size_t X>
+span<const uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
+as_bytes(span<T, X> s) noexcept {
+  return {reinterpret_cast<const uint8_t*>(s.data()), s.size_bytes()};
+}
+
+template <typename T,
+          size_t X,
+          typename = std::enable_if_t<!std::is_const<T>::value>>
+span<uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
+as_writable_bytes(span<T, X> s) noexcept {
+  return {reinterpret_cast<uint8_t*>(s.data()), s.size_bytes()};
+}
+
+// Type-deducing helpers for constructing a span.
+template <typename T>
+constexpr span<T> make_span(T* data, size_t size) noexcept {
+  return {data, size};
+}
+
+template <typename T>
+constexpr span<T> make_span(T* begin, T* end) noexcept {
+  return {begin, end};
+}
+
+template <typename T, size_t N>
+constexpr span<T, N> make_span(T (&array)[N]) noexcept {
+  return array;
+}
+
+template <typename T, size_t N>
+constexpr span<T, N> make_span(std::array<T, N>& array) noexcept {
+  return array;
+}
+
+template <typename T, size_t N>
+constexpr span<const T, N> make_span(const std::array<T, N>& array) noexcept {
+  return array;
+}
+
+template <typename Container,
+          typename T = typename Container::value_type,
+          typename = internal::EnableIfSpanCompatibleContainer<Container&, T>>
+constexpr span<T> make_span(Container& container) noexcept {
+  return container;
+}
+
+template <
+    typename Container,
+    typename T = const typename Container::value_type,
+    typename = internal::EnableIfSpanCompatibleContainer<const Container&, T>>
+constexpr span<T> make_span(const Container& container) noexcept {
+  return container;
+}
+
+template <typename T, size_t X>
+constexpr span<T, X> make_span(const span<T, X>& span) noexcept {
+  return span;
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_SPAN_H_
diff --git a/src/base/containers/stack.h b/src/base/containers/stack.h
new file mode 100644 (file)
index 0000000..1aaa879
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_STACK_H_
+#define BASE_CONTAINERS_STACK_H_
+
+#include <stack>
+
+#include "base/containers/circular_deque.h"
+
+namespace base {
+
+// Provides a definition of base::stack that's like std::stack but uses a
+// base::circular_queue instead of std::deque. Since std::stack is just a
+// wrapper for an underlying type, we can just provide a typedef for it that
+// defaults to the base circular_deque.
+template <class T, class Container = circular_deque<T>>
+using stack = std::stack<T, Container>;
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_STACK_H_
diff --git a/src/base/containers/vector_buffer.h b/src/base/containers/vector_buffer.h
new file mode 100644 (file)
index 0000000..7447126
--- /dev/null
@@ -0,0 +1,163 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_VECTOR_BUFFERS_H_
+#define BASE_CONTAINERS_VECTOR_BUFFERS_H_
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <type_traits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace base {
+namespace internal {
+
+// Internal implementation detail of base/containers.
+//
+// Implements a vector-like buffer that holds a certain capacity of T. Unlike
+// std::vector, VectorBuffer never constructs or destructs its arguments, and
+// can't change sizes. But it does implement templates to assist in efficient
+// moving and destruction of those items manually.
+//
+// In particular, the destructor function does not iterate over the items if
+// there is no destructor. Moves should be implemented as a memcpy/memmove for
+// trivially copyable objects (POD) otherwise, it should be a std::move if
+// possible, and as a last resort it falls back to a copy. This behavior is
+// similar to std::vector.
+//
+// No special consideration is done for noexcept move constructors since
+// we compile without exceptions.
+//
+// The current API does not support moving overlapping ranges.
+template <typename T>
+class VectorBuffer {
+ public:
+  constexpr VectorBuffer() = default;
+
+#if defined(__clang__)
+  // This constructor converts an uninitialized void* to a T* which triggers
+  // clang Control Flow Integrity. Since this is as-designed, disable.
+  __attribute__((no_sanitize("cfi-unrelated-cast", "vptr")))
+#endif
+  VectorBuffer(size_t count)
+      : buffer_(reinterpret_cast<T*>(malloc(sizeof(T) * count))),
+        capacity_(count) {
+  }
+  VectorBuffer(VectorBuffer&& other) noexcept
+      : buffer_(other.buffer_), capacity_(other.capacity_) {
+    other.buffer_ = nullptr;
+    other.capacity_ = 0;
+  }
+
+  ~VectorBuffer() { free(buffer_); }
+
+  VectorBuffer& operator=(VectorBuffer&& other) {
+    free(buffer_);
+    buffer_ = other.buffer_;
+    capacity_ = other.capacity_;
+
+    other.buffer_ = nullptr;
+    other.capacity_ = 0;
+    return *this;
+  }
+
+  size_t capacity() const { return capacity_; }
+
+  T& operator[](size_t i) { return buffer_[i]; }
+  const T& operator[](size_t i) const { return buffer_[i]; }
+  T* begin() { return buffer_; }
+  T* end() { return &buffer_[capacity_]; }
+
+  // DestructRange ------------------------------------------------------------
+
+  // Trivially destructible objects need not have their destructors called.
+  template <typename T2 = T,
+            typename std::enable_if<std::is_trivially_destructible<T2>::value,
+                                    int>::type = 0>
+  void DestructRange(T* begin, T* end) {}
+
+  // Non-trivially destructible objects must have their destructors called
+  // individually.
+  template <typename T2 = T,
+            typename std::enable_if<!std::is_trivially_destructible<T2>::value,
+                                    int>::type = 0>
+  void DestructRange(T* begin, T* end) {
+    while (begin != end) {
+      begin->~T();
+      begin++;
+    }
+  }
+
+  // MoveRange ----------------------------------------------------------------
+  //
+  // The destructor will be called (as necessary) for all moved types. The
+  // ranges must not overlap.
+  //
+  // The parameters and begin and end (one past the last) of the input buffer,
+  // and the address of the first element to copy to. There must be sufficient
+  // room in the destination for all items in the range [begin, end).
+
+  // Trivially copyable types can use memcpy. trivially copyable implies
+  // that there is a trivial destructor as we don't have to call it.
+  template <typename T2 = T,
+            typename std::enable_if<std::is_trivially_copyable<T2>::value,
+                                    int>::type = 0>
+  static void MoveRange(T* from_begin, T* from_end, T* to) {
+    DCHECK(!RangesOverlap(from_begin, from_end, to));
+    memcpy(to, from_begin, (from_end - from_begin) * sizeof(T));
+  }
+
+  // Not trivially copyable, but movable: call the move constructor and
+  // destruct the original.
+  template <typename T2 = T,
+            typename std::enable_if<std::is_move_constructible<T2>::value &&
+                                        !std::is_trivially_copyable<T2>::value,
+                                    int>::type = 0>
+  static void MoveRange(T* from_begin, T* from_end, T* to) {
+    DCHECK(!RangesOverlap(from_begin, from_end, to));
+    while (from_begin != from_end) {
+      new (to) T(std::move(*from_begin));
+      from_begin->~T();
+      from_begin++;
+      to++;
+    }
+  }
+
+  // Not movable, not trivially copyable: call the copy constructor and
+  // destruct the original.
+  template <typename T2 = T,
+            typename std::enable_if<!std::is_move_constructible<T2>::value &&
+                                        !std::is_trivially_copyable<T2>::value,
+                                    int>::type = 0>
+  static void MoveRange(T* from_begin, T* from_end, T* to) {
+    DCHECK(!RangesOverlap(from_begin, from_end, to));
+    while (from_begin != from_end) {
+      new (to) T(*from_begin);
+      from_begin->~T();
+      from_begin++;
+      to++;
+    }
+  }
+
+ private:
+  static bool RangesOverlap(const T* from_begin,
+                            const T* from_end,
+                            const T* to) {
+    return !(to >= from_end || to + (from_end - from_begin) <= from_begin);
+  }
+
+  T* buffer_ = nullptr;
+  size_t capacity_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(VectorBuffer);
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_VECTOR_BUFFERS_H_
diff --git a/src/base/environment.cc b/src/base/environment.cc
new file mode 100644 (file)
index 0000000..427e3e5
--- /dev/null
@@ -0,0 +1,243 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/environment.h"
+
+#include <stddef.h>
+
+#include <string_view>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <stdlib.h>
+#endif
+
+namespace base {
+
+namespace {
+
+class EnvironmentImpl : public Environment {
+ public:
+  bool GetVar(std::string_view variable_name, std::string* result) override {
+    if (GetVarImpl(variable_name, result))
+      return true;
+
+    // Some commonly used variable names are uppercase while others
+    // are lowercase, which is inconsistent. Let's try to be helpful
+    // and look for a variable name with the reverse case.
+    // I.e. HTTP_PROXY may be http_proxy for some users/systems.
+    char first_char = variable_name[0];
+    std::string alternate_case_var;
+    if (IsAsciiLower(first_char))
+      alternate_case_var = ToUpperASCII(variable_name);
+    else if (IsAsciiUpper(first_char))
+      alternate_case_var = ToLowerASCII(variable_name);
+    else
+      return false;
+    return GetVarImpl(alternate_case_var, result);
+  }
+
+  bool SetVar(std::string_view variable_name,
+              const std::string& new_value) override {
+    return SetVarImpl(variable_name, new_value);
+  }
+
+  bool UnSetVar(std::string_view variable_name) override {
+    return UnSetVarImpl(variable_name);
+  }
+
+ private:
+  bool GetVarImpl(std::string_view variable_name, std::string* result) {
+#if defined(OS_WIN)
+    DWORD value_length = ::GetEnvironmentVariable(
+        reinterpret_cast<LPCWSTR>(UTF8ToUTF16(variable_name).c_str()), nullptr,
+        0);
+    if (value_length == 0)
+      return false;
+    if (result) {
+      std::unique_ptr<char16_t[]> value(new char16_t[value_length]);
+      ::GetEnvironmentVariable(
+          reinterpret_cast<LPCWSTR>(UTF8ToUTF16(variable_name).c_str()),
+          reinterpret_cast<LPWSTR>(value.get()), value_length);
+      *result = UTF16ToUTF8(value.get());
+    }
+    return true;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    const char* env_value = getenv(variable_name.data());
+    if (!env_value)
+      return false;
+    // Note that the variable may be defined but empty.
+    if (result)
+      *result = env_value;
+    return true;
+#endif
+  }
+
+  bool SetVarImpl(std::string_view variable_name,
+                  const std::string& new_value) {
+#if defined(OS_WIN)
+    // On success, a nonzero value is returned.
+    return !!SetEnvironmentVariable(
+        reinterpret_cast<LPCWSTR>(UTF8ToUTF16(variable_name).c_str()),
+        reinterpret_cast<LPCWSTR>(UTF8ToUTF16(new_value).c_str()));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    // On success, zero is returned.
+    return !setenv(variable_name.data(), new_value.c_str(), 1);
+#endif
+  }
+
+  bool UnSetVarImpl(std::string_view variable_name) {
+#if defined(OS_WIN)
+    // On success, a nonzero value is returned.
+    return !!SetEnvironmentVariable(
+        reinterpret_cast<LPCWSTR>(UTF8ToUTF16(variable_name).c_str()), nullptr);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    // On success, zero is returned.
+    return !unsetenv(variable_name.data());
+#endif
+  }
+};
+
+// Parses a null-terminated input string of an environment block. The key is
+// placed into the given string, and the total length of the line, including
+// the terminating null, is returned.
+size_t ParseEnvLine(const NativeEnvironmentString::value_type* input,
+                    NativeEnvironmentString* key) {
+  // Skip to the equals or end of the string, this is the key.
+  size_t cur = 0;
+  while (input[cur] && input[cur] != '=')
+    cur++;
+  *key = NativeEnvironmentString(&input[0], cur);
+
+  // Now just skip to the end of the string.
+  while (input[cur])
+    cur++;
+  return cur + 1;
+}
+
+}  // namespace
+
+namespace env_vars {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+// On Posix systems, this variable contains the location of the user's home
+// directory. (e.g, /home/username/).
+const char kHome[] = "HOME";
+#endif
+
+}  // namespace env_vars
+
+Environment::~Environment() = default;
+
+// static
+std::unique_ptr<Environment> Environment::Create() {
+  return std::make_unique<EnvironmentImpl>();
+}
+
+bool Environment::HasVar(std::string_view variable_name) {
+  return GetVar(variable_name, nullptr);
+}
+
+#if defined(OS_WIN)
+
+std::u16string AlterEnvironment(const char16_t* env,
+                                const EnvironmentMap& changes) {
+  std::u16string result;
+
+  // First copy all unmodified values to the output.
+  size_t cur_env = 0;
+  std::u16string key;
+  while (env[cur_env]) {
+    const char16_t* line = &env[cur_env];
+    size_t line_length = ParseEnvLine(line, &key);
+
+    // Keep only values not specified in the change vector.
+    EnvironmentMap::const_iterator found_change = changes.find(key);
+    if (found_change == changes.end())
+      result.append(line, line_length);
+
+    cur_env += line_length;
+  }
+
+  // Now append all modified and new values.
+  for (EnvironmentMap::const_iterator i = changes.begin(); i != changes.end();
+       ++i) {
+    if (!i->second.empty()) {
+      result.append(i->first);
+      result.push_back('=');
+      result.append(i->second);
+      result.push_back(0);
+    }
+  }
+
+  // An additional null marks the end of the list. We always need a double-null
+  // in case nothing was added above.
+  if (result.empty())
+    result.push_back(0);
+  result.push_back(0);
+  return result;
+}
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+std::unique_ptr<char* []> AlterEnvironment(const char* const* const env,
+                                           const EnvironmentMap& changes) {
+  std::string value_storage;  // Holds concatenated null-terminated strings.
+  std::vector<size_t> result_indices;  // Line indices into value_storage.
+
+  // First build up all of the unchanged environment strings. These are
+  // null-terminated of the form "key=value".
+  std::string key;
+  for (size_t i = 0; env[i]; i++) {
+    size_t line_length = ParseEnvLine(env[i], &key);
+
+    // Keep only values not specified in the change vector.
+    EnvironmentMap::const_iterator found_change = changes.find(key);
+    if (found_change == changes.end()) {
+      result_indices.push_back(value_storage.size());
+      value_storage.append(env[i], line_length);
+    }
+  }
+
+  // Now append all modified and new values.
+  for (EnvironmentMap::const_iterator i = changes.begin(); i != changes.end();
+       ++i) {
+    if (!i->second.empty()) {
+      result_indices.push_back(value_storage.size());
+      value_storage.append(i->first);
+      value_storage.push_back('=');
+      value_storage.append(i->second);
+      value_storage.push_back(0);
+    }
+  }
+
+  size_t pointer_count_required =
+      result_indices.size() + 1 +  // Null-terminated array of pointers.
+      (value_storage.size() + sizeof(char*) - 1) / sizeof(char*);  // Buffer.
+  std::unique_ptr<char*[]> result(new char*[pointer_count_required]);
+
+  // The string storage goes after the array of pointers.
+  char* storage_data =
+      reinterpret_cast<char*>(&result.get()[result_indices.size() + 1]);
+  if (!value_storage.empty())
+    memcpy(storage_data, value_storage.data(), value_storage.size());
+
+  // Fill array of pointers at the beginning of the result.
+  for (size_t i = 0; i < result_indices.size(); i++)
+    result[i] = &storage_data[result_indices[i]];
+  result[result_indices.size()] = 0;  // Null terminator.
+
+  return result;
+}
+
+#endif  // OS_WIN
+
+}  // namespace base
diff --git a/src/base/environment.h b/src/base/environment.h
new file mode 100644 (file)
index 0000000..f9a2f55
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ENVIRONMENT_H_
+#define BASE_ENVIRONMENT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "util/build_config.h"
+
+namespace base {
+
+namespace env_vars {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+extern const char kHome[];
+#endif
+
+}  // namespace env_vars
+
+class Environment {
+ public:
+  virtual ~Environment();
+
+  // Returns the appropriate platform-specific instance.
+  static std::unique_ptr<Environment> Create();
+
+  // Gets an environment variable's value and stores it in |result|.
+  // Returns false if the key is unset.
+  virtual bool GetVar(std::string_view variable_name, std::string* result) = 0;
+
+  // Syntactic sugar for GetVar(variable_name, nullptr);
+  virtual bool HasVar(std::string_view variable_name);
+
+  // Returns true on success, otherwise returns false.
+  virtual bool SetVar(std::string_view variable_name,
+                      const std::string& new_value) = 0;
+
+  // Returns true on success, otherwise returns false.
+  virtual bool UnSetVar(std::string_view variable_name) = 0;
+};
+
+#if defined(OS_WIN)
+
+typedef std::u16string NativeEnvironmentString;
+typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
+    EnvironmentMap;
+
+// Returns a modified environment vector constructed from the given environment
+// and the list of changes given in |changes|. Each key in the environment is
+// matched against the first element of the pairs. In the event of a match, the
+// value is replaced by the second of the pair, unless the second is empty, in
+// which case the key-value is removed.
+//
+// This Windows version takes and returns a Windows-style environment block
+// which is a concatenated list of null-terminated 16-bit strings. The end is
+// marked by a double-null terminator. The size of the returned string will
+// include the terminators.
+std::u16string AlterEnvironment(const char16_t* env,
+                                const EnvironmentMap& changes);
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+typedef std::string NativeEnvironmentString;
+typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
+    EnvironmentMap;
+
+// See general comments for the Windows version above.
+//
+// This Posix version takes and returns a Posix-style environment block, which
+// is a null-terminated list of pointers to null-terminated strings. The
+// returned array will have appended to it the storage for the array itself so
+// there is only one pointer to manage, but this means that you can't copy the
+// array without keeping the original around.
+std::unique_ptr<char*[]> AlterEnvironment(const char* const* env,
+                                          const EnvironmentMap& changes);
+
+#endif
+
+}  // namespace base
+
+#endif  // BASE_ENVIRONMENT_H_
diff --git a/src/base/files/file.cc b/src/base/files/file.cc
new file mode 100644 (file)
index 0000000..d82911a
--- /dev/null
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "util/build_config.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
+#endif
+
+namespace base {
+
+File::Info::Info() : size(0), is_directory(false), is_symbolic_link(false) {}
+
+File::Info::~Info() = default;
+
+File::File()
+    : error_details_(FILE_ERROR_FAILED), created_(false) {}
+
+File::File(const FilePath& path, uint32_t flags)
+    : error_details_(FILE_OK), created_(false) {
+  Initialize(path, flags);
+}
+
+File::File(PlatformFile platform_file)
+    : file_(platform_file),
+      error_details_(FILE_OK),
+      created_(false) {
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+  DCHECK_GE(platform_file, -1);
+#endif
+}
+
+File::File(Error error_details)
+    : error_details_(error_details), created_(false) {}
+
+File::File(File&& other)
+    : file_(other.TakePlatformFile()),
+      error_details_(other.error_details()),
+      created_(other.created()) {}
+
+File::~File() {
+  // Go through the AssertIOAllowed logic.
+  Close();
+}
+
+File& File::operator=(File&& other) {
+  Close();
+  SetPlatformFile(other.TakePlatformFile());
+  error_details_ = other.error_details();
+  created_ = other.created();
+  return *this;
+}
+
+void File::Initialize(const FilePath& path, uint32_t flags) {
+  if (path.ReferencesParent()) {
+#if defined(OS_WIN)
+    ::SetLastError(ERROR_ACCESS_DENIED);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    errno = EACCES;
+#else
+#error Unsupported platform
+#endif
+    error_details_ = FILE_ERROR_ACCESS_DENIED;
+    return;
+  }
+  DoInitialize(path, flags);
+}
+
+std::string File::ErrorToString(Error error) {
+  switch (error) {
+    case FILE_OK:
+      return "FILE_OK";
+    case FILE_ERROR_FAILED:
+      return "FILE_ERROR_FAILED";
+    case FILE_ERROR_IN_USE:
+      return "FILE_ERROR_IN_USE";
+    case FILE_ERROR_EXISTS:
+      return "FILE_ERROR_EXISTS";
+    case FILE_ERROR_NOT_FOUND:
+      return "FILE_ERROR_NOT_FOUND";
+    case FILE_ERROR_ACCESS_DENIED:
+      return "FILE_ERROR_ACCESS_DENIED";
+    case FILE_ERROR_TOO_MANY_OPENED:
+      return "FILE_ERROR_TOO_MANY_OPENED";
+    case FILE_ERROR_NO_MEMORY:
+      return "FILE_ERROR_NO_MEMORY";
+    case FILE_ERROR_NO_SPACE:
+      return "FILE_ERROR_NO_SPACE";
+    case FILE_ERROR_NOT_A_DIRECTORY:
+      return "FILE_ERROR_NOT_A_DIRECTORY";
+    case FILE_ERROR_INVALID_OPERATION:
+      return "FILE_ERROR_INVALID_OPERATION";
+    case FILE_ERROR_SECURITY:
+      return "FILE_ERROR_SECURITY";
+    case FILE_ERROR_ABORT:
+      return "FILE_ERROR_ABORT";
+    case FILE_ERROR_NOT_A_FILE:
+      return "FILE_ERROR_NOT_A_FILE";
+    case FILE_ERROR_NOT_EMPTY:
+      return "FILE_ERROR_NOT_EMPTY";
+    case FILE_ERROR_INVALID_URL:
+      return "FILE_ERROR_INVALID_URL";
+    case FILE_ERROR_IO:
+      return "FILE_ERROR_IO";
+    case FILE_ERROR_MAX:
+      break;
+  }
+
+  NOTREACHED();
+  return "";
+}
+
+}  // namespace base
diff --git a/src/base/files/file.h b/src/base/files/file.h
new file mode 100644 (file)
index 0000000..b286f69
--- /dev/null
@@ -0,0 +1,289 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_FILE_H_
+#define BASE_FILES_FILE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/platform_file.h"
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "util/build_config.h"
+#include "util/ticks.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/stat.h>
+#endif
+
+namespace base {
+
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+typedef struct stat stat_wrapper_t;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+typedef struct stat64 stat_wrapper_t;
+#endif
+
+// Thin wrapper around an OS-level file.
+// Note that this class does not provide any support for asynchronous IO.
+//
+// Note about const: this class does not attempt to determine if the underlying
+// file system object is affected by a particular method in order to consider
+// that method const or not. Only methods that deal with member variables in an
+// obvious non-modifying way are marked as const. Any method that forward calls
+// to the OS is not considered const, even if there is no apparent change to
+// member variables.
+class File {
+ public:
+  // FLAG_(OPEN|CREATE).* are mutually exclusive. You should specify exactly one
+  // of the five (possibly combining with other flags) when opening or creating
+  // a file.
+  enum Flags {
+    FLAG_OPEN = 1 << 0,            // Opens a file, only if it exists.
+    FLAG_CREATE_ALWAYS = 1 << 3,   // May overwrite an old file.
+    FLAG_READ = 1 << 4,
+    FLAG_WRITE = 1 << 5,
+  };
+
+  // This enum has been recorded in multiple histograms using PlatformFileError
+  // enum. If the order of the fields needs to change, please ensure that those
+  // histograms are obsolete or have been moved to a different enum.
+  //
+  // FILE_ERROR_ACCESS_DENIED is returned when a call fails because of a
+  // filesystem restriction. FILE_ERROR_SECURITY is returned when a browser
+  // policy doesn't allow the operation to be executed.
+  enum Error {
+    FILE_OK = 0,
+    FILE_ERROR_FAILED = -1,
+    FILE_ERROR_IN_USE = -2,
+    FILE_ERROR_EXISTS = -3,
+    FILE_ERROR_NOT_FOUND = -4,
+    FILE_ERROR_ACCESS_DENIED = -5,
+    FILE_ERROR_TOO_MANY_OPENED = -6,
+    FILE_ERROR_NO_MEMORY = -7,
+    FILE_ERROR_NO_SPACE = -8,
+    FILE_ERROR_NOT_A_DIRECTORY = -9,
+    FILE_ERROR_INVALID_OPERATION = -10,
+    FILE_ERROR_SECURITY = -11,
+    FILE_ERROR_ABORT = -12,
+    FILE_ERROR_NOT_A_FILE = -13,
+    FILE_ERROR_NOT_EMPTY = -14,
+    FILE_ERROR_INVALID_URL = -15,
+    FILE_ERROR_IO = -16,
+    // Put new entries here and increment FILE_ERROR_MAX.
+    FILE_ERROR_MAX = -17
+  };
+
+  // This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux.
+  enum Whence { FROM_BEGIN = 0, FROM_CURRENT = 1, FROM_END = 2 };
+
+  // Used to hold information about a given file.
+  // If you add more fields to this structure (platform-specific fields are OK),
+  // make sure to update all functions that use it in file_util_{win|posix}.cc,
+  // too, and the ParamTraits<base::File::Info> implementation in
+  // ipc/ipc_message_utils.cc.
+  struct Info {
+    Info();
+    ~Info();
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+    // Fills this struct with values from |stat_info|.
+    void FromStat(const stat_wrapper_t& stat_info);
+#endif
+
+    // The size of the file in bytes.  Undefined when is_directory is true.
+    int64_t size;
+
+    // True if the file corresponds to a directory.
+    bool is_directory;
+
+    // True if the file corresponds to a symbolic link.  For Windows currently
+    // not supported and thus always false.
+    bool is_symbolic_link;
+
+    // The last modified time of a file.
+    Ticks last_modified;
+
+    // The last accessed time of a file.
+    Ticks last_accessed;
+
+    // The creation time of a file.
+    Ticks creation_time;
+  };
+
+  File();
+
+  // Creates or opens the given file. This will fail with 'access denied' if the
+  // |path| contains path traversal ('..') components.
+  File(const FilePath& path, uint32_t flags);
+
+  // Takes ownership of |platform_file|.
+  explicit File(PlatformFile platform_file);
+
+  // Creates an object with a specific error_details code.
+  explicit File(Error error_details);
+
+  File(File&& other);
+
+  ~File();
+
+  File& operator=(File&& other);
+
+  // Creates or opens the given file.
+  void Initialize(const FilePath& path, uint32_t flags);
+
+  // Returns |true| if the handle / fd wrapped by this object is valid.  This
+  // method doesn't interact with the file system (and is safe to be called from
+  // ThreadRestrictions::SetIOAllowed(false) threads).
+  bool IsValid() const;
+
+  // Returns true if a new file was created (or an old one truncated to zero
+  // length to simulate a new file, which can happen with
+  // FLAG_CREATE_ALWAYS), and false otherwise.
+  bool created() const { return created_; }
+
+  // Returns the OS result of opening this file. Note that the way to verify
+  // the success of the operation is to use IsValid(), not this method:
+  //   File file(path, flags);
+  //   if (!file.IsValid())
+  //     return;
+  Error error_details() const { return error_details_; }
+
+  PlatformFile GetPlatformFile() const;
+  PlatformFile TakePlatformFile();
+
+  // Destroying this object closes the file automatically.
+  void Close();
+
+  // Changes current position in the file to an |offset| relative to an origin
+  // defined by |whence|. Returns the resultant current position in the file
+  // (relative to the start) or -1 in case of error.
+  int64_t Seek(Whence whence, int64_t offset);
+
+  // Reads the given number of bytes (or until EOF is reached) starting with the
+  // given offset. Returns the number of bytes read, or -1 on error. Note that
+  // this function makes a best effort to read all data on all platforms, so it
+  // is not intended for stream oriented files but instead for cases when the
+  // normal expectation is that actually |size| bytes are read unless there is
+  // an error.
+  int Read(int64_t offset, char* data, int size);
+
+  // Same as above but without seek.
+  int ReadAtCurrentPos(char* data, int size);
+
+  // Reads the given number of bytes (or until EOF is reached) starting with the
+  // given offset, but does not make any effort to read all data on all
+  // platforms. Returns the number of bytes read, or -1 on error.
+  int ReadNoBestEffort(int64_t offset, char* data, int size);
+
+  // Same as above but without seek.
+  int ReadAtCurrentPosNoBestEffort(char* data, int size);
+
+  // Writes the given buffer into the file at the given offset, overwriting any
+  // data that was previously there. Returns the number of bytes written, or -1
+  // on error. Note that this function makes a best effort to write all data on
+  // all platforms. |data| can be nullptr when |size| is 0.
+  int Write(int64_t offset, const char* data, int size);
+
+  // Save as above but without seek.
+  int WriteAtCurrentPos(const char* data, int size);
+
+  // Save as above but does not make any effort to write all data on all
+  // platforms. Returns the number of bytes written, or -1 on error.
+  int WriteAtCurrentPosNoBestEffort(const char* data, int size);
+
+  // Returns the current size of this file, or a negative number on failure.
+  int64_t GetLength();
+
+  // Truncates the file to the given length. If |length| is greater than the
+  // current size of the file, the file is extended with zeros. If the file
+  // doesn't exist, |false| is returned.
+  bool SetLength(int64_t length);
+
+  // Instructs the filesystem to flush the file to disk. (POSIX: fsync, Windows:
+  // FlushFileBuffers).
+  // Calling Flush() does not guarantee file integrity and thus is not a valid
+  // substitute for file integrity checks and recovery codepaths for malformed
+  // files. It can also be *really* slow, so avoid blocking on Flush(),
+  // especially please don't block shutdown on Flush().
+  // Latency percentiles of Flush() across all platforms as of July 2016:
+  // 50 %     > 5 ms
+  // 10 %     > 58 ms
+  //  1 %     > 357 ms
+  //  0.1 %   > 1.8 seconds
+  //  0.01 %  > 7.6 seconds
+  bool Flush();
+
+  // Returns some basic information for the given file.
+  bool GetInfo(Info* info);
+
+#if !defined(OS_FUCHSIA)  // Fuchsia's POSIX API does not support file locking.
+
+  // Attempts to take an exclusive write lock on the file. Returns immediately
+  // (i.e. does not wait for another process to unlock the file). If the lock
+  // was obtained, the result will be FILE_OK. A lock only guarantees
+  // that other processes may not also take a lock on the same file with the
+  // same API - it may still be opened, renamed, unlinked, etc.
+  //
+  // Common semantics:
+  //  * Locks are held by processes, but not inherited by child processes.
+  //  * Locks are released by the OS on file close or process termination.
+  //  * Locks are reliable only on local filesystems.
+  //  * Duplicated file handles may also write to locked files.
+  // Windows-specific semantics:
+  //  * Locks are mandatory for read/write APIs, advisory for mapping APIs.
+  //  * Within a process, locking the same file (by the same or new handle)
+  //    will fail.
+  // POSIX-specific semantics:
+  //  * Locks are advisory only.
+  //  * Within a process, locking the same file (by the same or new handle)
+  //    will succeed.
+  //  * Closing any descriptor on a given file releases the lock.
+  Error Lock();
+
+  // Unlock a file previously locked.
+  Error Unlock();
+
+#endif  // !defined(OS_FUCHSIA)
+
+  // Returns a new object referencing this file for use within the current
+  // process.
+  File Duplicate() const;
+
+#if defined(OS_WIN)
+  static Error OSErrorToFileError(DWORD last_error);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  static Error OSErrorToFileError(int saved_errno);
+#endif
+
+  // Gets the last global error (errno or GetLastError()) and converts it to the
+  // closest base::File::Error equivalent via OSErrorToFileError(). The returned
+  // value is only trustworthy immediately after another base::File method
+  // fails. base::File never resets the global error to zero.
+  static Error GetLastFileError();
+
+  // Converts an error value to a human-readable form. Used for logging.
+  static std::string ErrorToString(Error error);
+
+ private:
+  // Creates or opens the given file. Only called if |path| has no
+  // traversal ('..') components.
+  void DoInitialize(const FilePath& path, uint32_t flags);
+
+  void SetPlatformFile(PlatformFile file);
+
+  ScopedPlatformFile file_;
+
+  Error error_details_;
+  bool created_;
+
+  DISALLOW_COPY_AND_ASSIGN(File);
+};
+
+}  // namespace base
+
+#endif  // BASE_FILES_FILE_H_
diff --git a/src/base/files/file_enumerator.cc b/src/base/files/file_enumerator.cc
new file mode 100644 (file)
index 0000000..9dfb2ba
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_enumerator.h"
+
+#include "base/files/file_util.h"
+
+namespace base {
+
+FileEnumerator::FileInfo::~FileInfo() = default;
+
+bool FileEnumerator::ShouldSkip(const FilePath& path) {
+  FilePath::StringType basename = path.BaseName().value();
+  return basename == FILE_PATH_LITERAL(".") ||
+         (basename == FILE_PATH_LITERAL("..") &&
+          !(INCLUDE_DOT_DOT & file_type_));
+}
+
+bool FileEnumerator::IsTypeMatched(bool is_dir) const {
+  return (file_type_ &
+          (is_dir ? FileEnumerator::DIRECTORIES : FileEnumerator::FILES)) != 0;
+}
+
+}  // namespace base
diff --git a/src/base/files/file_enumerator.h b/src/base/files/file_enumerator.h
new file mode 100644 (file)
index 0000000..3a2a823
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_FILE_ENUMERATOR_H_
+#define BASE_FILES_FILE_ENUMERATOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/containers/stack.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "util/build_config.h"
+#include "util/ticks.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+namespace base {
+
+// A class for enumerating the files in a provided path. The order of the
+// results is not guaranteed.
+//
+// This is blocking. Do not use on critical threads.
+//
+// Example:
+//
+//   base::FileEnumerator enum(my_dir, false, base::FileEnumerator::FILES,
+//                             FILE_PATH_LITERAL("*.txt"));
+//   for (base::FilePath name = enum.Next(); !name.empty(); name = enum.Next())
+//     ...
+class FileEnumerator {
+ public:
+  // Note: copy & assign supported.
+  class FileInfo {
+   public:
+    FileInfo();
+    ~FileInfo();
+
+    bool IsDirectory() const;
+
+    // The name of the file. This will not include any path information. This
+    // is in contrast to the value returned by FileEnumerator.Next() which
+    // includes the |root_path| passed into the FileEnumerator constructor.
+    FilePath GetName() const;
+
+    int64_t GetSize() const;
+    Ticks GetLastModifiedTime() const;
+
+#if defined(OS_WIN)
+    // Note that the cAlternateFileName (used to hold the "short" 8.3 name)
+    // of the WIN32_FIND_DATA will be empty. Since we don't use short file
+    // names, we tell Windows to omit it which speeds up the query slightly.
+    const WIN32_FIND_DATA& find_data() const { return find_data_; }
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    const struct stat& stat() const { return stat_; }
+#endif
+
+   private:
+    friend class FileEnumerator;
+
+#if defined(OS_WIN)
+    WIN32_FIND_DATA find_data_;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+    struct stat stat_;
+    FilePath filename_;
+#endif
+  };
+
+  enum FileType {
+    FILES = 1 << 0,
+    DIRECTORIES = 1 << 1,
+    INCLUDE_DOT_DOT = 1 << 2,
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+    SHOW_SYM_LINKS = 1 << 4,
+#endif
+  };
+
+  // Search policy for intermediate folders.
+  enum class FolderSearchPolicy {
+    // Recursive search will pass through folders whose names match the
+    // pattern. Inside each one, all files will be returned. Folders with names
+    // that do not match the pattern will be ignored within their interior.
+    MATCH_ONLY,
+    // Recursive search will pass through every folder and perform pattern
+    // matching inside each one.
+    ALL,
+  };
+
+  // |root_path| is the starting directory to search for. It may or may not end
+  // in a slash.
+  //
+  // If |recursive| is true, this will enumerate all matches in any
+  // subdirectories matched as well. It does a breadth-first search, so all
+  // files in one directory will be returned before any files in a
+  // subdirectory.
+  //
+  // |file_type|, a bit mask of FileType, specifies whether the enumerator
+  // should match files, directories, or both.
+  //
+  // |pattern| is an optional pattern for which files to match. This
+  // works like shell globbing. For example, "*.txt" or "Foo???.doc".
+  // However, be careful in specifying patterns that aren't cross platform
+  // since the underlying code uses OS-specific matching routines.  In general,
+  // Windows matching is less featureful than others, so test there first.
+  // If unspecified, this will match all files.
+  FileEnumerator(const FilePath& root_path, bool recursive, int file_type);
+  FileEnumerator(const FilePath& root_path,
+                 bool recursive,
+                 int file_type,
+                 const FilePath::StringType& pattern);
+  FileEnumerator(const FilePath& root_path,
+                 bool recursive,
+                 int file_type,
+                 const FilePath::StringType& pattern,
+                 FolderSearchPolicy folder_search_policy);
+  ~FileEnumerator();
+
+  // Returns the next file or an empty string if there are no more results.
+  //
+  // The returned path will incorporate the |root_path| passed in the
+  // constructor: "<root_path>/file_name.txt". If the |root_path| is absolute,
+  // then so will be the result of Next().
+  FilePath Next();
+
+  // Write the file info into |info|.
+  FileInfo GetInfo() const;
+
+ private:
+  // Returns true if the given path should be skipped in enumeration.
+  bool ShouldSkip(const FilePath& path);
+
+  bool IsTypeMatched(bool is_dir) const;
+
+  bool IsPatternMatched(const FilePath& src) const;
+
+#if defined(OS_WIN)
+  // True when find_data_ is valid.
+  bool has_find_data_ = false;
+  WIN32_FIND_DATA find_data_;
+  HANDLE find_handle_ = INVALID_HANDLE_VALUE;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  // The files in the current directory
+  std::vector<FileInfo> directory_entries_;
+
+  // The next entry to use from the directory_entries_ vector
+  size_t current_directory_entry_;
+#endif
+  FilePath root_path_;
+  const bool recursive_;
+  const int file_type_;
+  FilePath::StringType pattern_;
+  const FolderSearchPolicy folder_search_policy_;
+
+  // A stack that keeps track of which subdirectories we still need to
+  // enumerate in the breadth-first search.
+  base::stack<FilePath> pending_paths_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileEnumerator);
+};
+
+}  // namespace base
+
+#endif  // BASE_FILES_FILE_ENUMERATOR_H_
diff --git a/src/base/files/file_enumerator_posix.cc b/src/base/files/file_enumerator_posix.cc
new file mode 100644 (file)
index 0000000..41d52b8
--- /dev/null
@@ -0,0 +1,170 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_enumerator.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "base/logging.h"
+#include "util/build_config.h"
+
+namespace base {
+namespace {
+
+void GetStat(const FilePath& path, bool show_links, struct stat* st) {
+  DCHECK(st);
+  const int res = show_links ? lstat(path.value().c_str(), st)
+                             : stat(path.value().c_str(), st);
+  if (res < 0) {
+    // Print the stat() error message unless it was ENOENT and we're following
+    // symlinks.
+    if (!(errno == ENOENT && !show_links))
+      DPLOG(ERROR) << "Couldn't stat" << path.value();
+    memset(st, 0, sizeof(*st));
+  }
+}
+
+}  // namespace
+
+// FileEnumerator::FileInfo ----------------------------------------------------
+
+FileEnumerator::FileInfo::FileInfo() {
+  memset(&stat_, 0, sizeof(stat_));
+}
+
+bool FileEnumerator::FileInfo::IsDirectory() const {
+  return S_ISDIR(stat_.st_mode);
+}
+
+FilePath FileEnumerator::FileInfo::GetName() const {
+  return filename_;
+}
+
+int64_t FileEnumerator::FileInfo::GetSize() const {
+  return stat_.st_size;
+}
+
+Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
+  return stat_.st_mtime;
+}
+
+// FileEnumerator --------------------------------------------------------------
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type)
+    : FileEnumerator(root_path,
+                     recursive,
+                     file_type,
+                     FilePath::StringType(),
+                     FolderSearchPolicy::MATCH_ONLY) {}
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type,
+                               const FilePath::StringType& pattern)
+    : FileEnumerator(root_path,
+                     recursive,
+                     file_type,
+                     pattern,
+                     FolderSearchPolicy::MATCH_ONLY) {}
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type,
+                               const FilePath::StringType& pattern,
+                               FolderSearchPolicy folder_search_policy)
+    : current_directory_entry_(0),
+      root_path_(root_path),
+      recursive_(recursive),
+      file_type_(file_type),
+      pattern_(pattern),
+      folder_search_policy_(folder_search_policy) {
+  // INCLUDE_DOT_DOT must not be specified if recursive.
+  DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
+
+  pending_paths_.push(root_path);
+}
+
+FileEnumerator::~FileEnumerator() = default;
+
+FilePath FileEnumerator::Next() {
+  ++current_directory_entry_;
+
+  // While we've exhausted the entries in the current directory, do the next
+  while (current_directory_entry_ >= directory_entries_.size()) {
+    if (pending_paths_.empty())
+      return FilePath();
+
+    root_path_ = pending_paths_.top();
+    root_path_ = root_path_.StripTrailingSeparators();
+    pending_paths_.pop();
+
+    DIR* dir = opendir(root_path_.value().c_str());
+    if (!dir)
+      continue;
+
+    directory_entries_.clear();
+
+    current_directory_entry_ = 0;
+    struct dirent* dent;
+    while ((dent = readdir(dir))) {
+      FileInfo info;
+      info.filename_ = FilePath(dent->d_name);
+
+      if (ShouldSkip(info.filename_))
+        continue;
+
+      const bool is_pattern_matched = IsPatternMatched(info.filename_);
+
+      // MATCH_ONLY policy enumerates files and directories which matching
+      // pattern only. So we can early skip further checks.
+      if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
+          !is_pattern_matched)
+        continue;
+
+      // Do not call OS stat/lstat if there is no sense to do it. If pattern is
+      // not matched (file will not appear in results) and search is not
+      // recursive (possible directory will not be added to pending paths) -
+      // there is no sense to obtain item below.
+      if (!recursive_ && !is_pattern_matched)
+        continue;
+
+      const FilePath full_path = root_path_.Append(info.filename_);
+      GetStat(full_path, file_type_ & SHOW_SYM_LINKS, &info.stat_);
+
+      const bool is_dir = info.IsDirectory();
+
+      if (recursive_ && is_dir)
+        pending_paths_.push(full_path);
+
+      if (is_pattern_matched && IsTypeMatched(is_dir))
+        directory_entries_.push_back(std::move(info));
+    }
+    closedir(dir);
+
+    // MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
+    // ALL policy enumerates files in all subfolders by origin pattern.
+    if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
+      pattern_.clear();
+  }
+
+  return root_path_.Append(
+      directory_entries_[current_directory_entry_].filename_);
+}
+
+FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
+  return directory_entries_[current_directory_entry_];
+}
+
+bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
+  return pattern_.empty() ||
+         !fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
+}
+
+}  // namespace base
diff --git a/src/base/files/file_enumerator_win.cc b/src/base/files/file_enumerator_win.cc
new file mode 100644 (file)
index 0000000..aa884ea
--- /dev/null
@@ -0,0 +1,193 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_enumerator.h"
+
+#include <shlwapi.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "base/logging.h"
+#include "base/win/win_util.h"
+
+namespace base {
+
+namespace {
+
+FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,
+                           const FilePath& root_path,
+                           const FilePath::StringType& pattern) {
+  // MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy
+  // collects all files and filters them manually.
+  switch (policy) {
+    case FileEnumerator::FolderSearchPolicy::MATCH_ONLY:
+      return root_path.Append(pattern);
+    case FileEnumerator::FolderSearchPolicy::ALL:
+      return root_path.Append(u"*");
+  }
+  NOTREACHED();
+  return {};
+}
+
+}  // namespace
+
+// FileEnumerator::FileInfo ----------------------------------------------------
+
+FileEnumerator::FileInfo::FileInfo() {
+  memset(&find_data_, 0, sizeof(find_data_));
+}
+
+bool FileEnumerator::FileInfo::IsDirectory() const {
+  return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+}
+
+FilePath FileEnumerator::FileInfo::GetName() const {
+  return FilePath(reinterpret_cast<const char16_t*>(find_data_.cFileName));
+}
+
+int64_t FileEnumerator::FileInfo::GetSize() const {
+  ULARGE_INTEGER size;
+  size.HighPart = find_data_.nFileSizeHigh;
+  size.LowPart = find_data_.nFileSizeLow;
+  DCHECK_LE(size.QuadPart,
+            static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max()));
+  return static_cast<int64_t>(size.QuadPart);
+}
+
+Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
+  return *reinterpret_cast<const uint64_t*>(&find_data_.ftLastWriteTime);
+}
+
+// FileEnumerator --------------------------------------------------------------
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type)
+    : FileEnumerator(root_path,
+                     recursive,
+                     file_type,
+                     FilePath::StringType(),
+                     FolderSearchPolicy::MATCH_ONLY) {}
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type,
+                               const FilePath::StringType& pattern)
+    : FileEnumerator(root_path,
+                     recursive,
+                     file_type,
+                     pattern,
+                     FolderSearchPolicy::MATCH_ONLY) {}
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+                               bool recursive,
+                               int file_type,
+                               const FilePath::StringType& pattern,
+                               FolderSearchPolicy folder_search_policy)
+    : recursive_(recursive),
+      file_type_(file_type),
+      pattern_(!pattern.empty() ? pattern : u"*"),
+      folder_search_policy_(folder_search_policy) {
+  // INCLUDE_DOT_DOT must not be specified if recursive.
+  DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
+  memset(&find_data_, 0, sizeof(find_data_));
+  pending_paths_.push(root_path);
+}
+
+FileEnumerator::~FileEnumerator() {
+  if (find_handle_ != INVALID_HANDLE_VALUE)
+    FindClose(find_handle_);
+}
+
+FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
+  if (!has_find_data_) {
+    NOTREACHED();
+    return FileInfo();
+  }
+  FileInfo ret;
+  memcpy(&ret.find_data_, &find_data_, sizeof(find_data_));
+  return ret;
+}
+
+FilePath FileEnumerator::Next() {
+  while (has_find_data_ || !pending_paths_.empty()) {
+    if (!has_find_data_) {
+      // The last find FindFirstFile operation is done, prepare a new one.
+      root_path_ = pending_paths_.top();
+      pending_paths_.pop();
+
+      // Start a new find operation.
+      const FilePath src =
+          BuildSearchFilter(folder_search_policy_, root_path_, pattern_);
+      find_handle_ = FindFirstFileEx(ToWCharT(&src.value()),
+                                     FindExInfoBasic,  // Omit short name.
+                                     &find_data_, FindExSearchNameMatch,
+                                     nullptr, FIND_FIRST_EX_LARGE_FETCH);
+      has_find_data_ = true;
+    } else {
+      // Search for the next file/directory.
+      if (!FindNextFile(find_handle_, &find_data_)) {
+        FindClose(find_handle_);
+        find_handle_ = INVALID_HANDLE_VALUE;
+      }
+    }
+
+    if (INVALID_HANDLE_VALUE == find_handle_) {
+      has_find_data_ = false;
+
+      // MATCH_ONLY policy clears pattern for matched subfolders. ALL policy
+      // applies pattern for all subfolders.
+      if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) {
+        // This is reached when we have finished a directory and are advancing
+        // to the next one in the queue. We applied the pattern (if any) to the
+        // files in the root search directory, but for those directories which
+        // were matched, we want to enumerate all files inside them. This will
+        // happen when the handle is empty.
+        pattern_ = u"*";
+      }
+
+      continue;
+    }
+
+    const FilePath filename(reinterpret_cast<char16_t*>(find_data_.cFileName));
+    if (ShouldSkip(filename))
+      continue;
+
+    const bool is_dir =
+        (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+    const FilePath abs_path = root_path_.Append(filename);
+
+    // Check if directory should be processed recursive.
+    if (is_dir && recursive_) {
+      // If |cur_file| is a directory, and we are doing recursive searching,
+      // add it to pending_paths_ so we scan it after we finish scanning this
+      // directory. However, don't do recursion through reparse points or we
+      // may end up with an infinite cycle.
+      DWORD attributes = GetFileAttributes(ToWCharT(&abs_path.value()));
+      if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
+        pending_paths_.push(abs_path);
+    }
+
+    if (IsTypeMatched(is_dir) && IsPatternMatched(filename))
+      return abs_path;
+  }
+  return FilePath();
+}
+
+bool FileEnumerator::IsPatternMatched(const FilePath& src) const {
+  switch (folder_search_policy_) {
+    case FolderSearchPolicy::MATCH_ONLY:
+      // MATCH_ONLY policy filters by pattern on search request, so all found
+      // files already fits to pattern.
+      return true;
+    case FolderSearchPolicy::ALL:
+      // ALL policy enumerates all files, we need to check pattern match
+      // manually.
+      return PathMatchSpec(ToWCharT(&src.value()), ToWCharT(&pattern_)) == TRUE;
+  }
+  NOTREACHED();
+  return false;
+}
+
+}  // namespace base
diff --git a/src/base/files/file_path.cc b/src/base/files/file_path.cc
new file mode 100644 (file)
index 0000000..d59b8ea
--- /dev/null
@@ -0,0 +1,639 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_cftyperef.h"
+#include "base/third_party/icu/icu_utf.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+namespace base {
+
+using StringType = FilePath::StringType;
+using StringViewType = FilePath::StringViewType;
+
+namespace {
+
+const char* const kCommonDoubleExtensionSuffixes[] = {"gz", "z", "bz2", "bz"};
+const char* const kCommonDoubleExtensions[] = {"user.js"};
+
+const FilePath::CharType kStringTerminator = FILE_PATH_LITERAL('\0');
+
+// If this FilePath contains a drive letter specification, returns the
+// position of the last character of the drive letter specification,
+// otherwise returns npos.  This can only be true on Windows, when a pathname
+// begins with a letter followed by a colon.  On other platforms, this always
+// returns npos.
+StringViewType::size_type FindDriveLetter(StringViewType path) {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  // This is dependent on an ASCII-based character set, but that's a
+  // reasonable assumption.  iswalpha can be too inclusive here.
+  if (path.length() >= 2 && path[1] == L':' &&
+      ((path[0] >= L'A' && path[0] <= L'Z') ||
+       (path[0] >= L'a' && path[0] <= L'z'))) {
+    return 1;
+  }
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+  return StringType::npos;
+}
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+bool EqualDriveLetterCaseInsensitive(StringViewType a, StringViewType b) {
+  size_t a_letter_pos = FindDriveLetter(a);
+  size_t b_letter_pos = FindDriveLetter(b);
+
+  if (a_letter_pos == StringType::npos || b_letter_pos == StringType::npos)
+    return a == b;
+
+  StringViewType a_letter(a.substr(0, a_letter_pos + 1));
+  StringViewType b_letter(b.substr(0, b_letter_pos + 1));
+  if (!StartsWith(a_letter, b_letter, CompareCase::INSENSITIVE_ASCII))
+    return false;
+
+  StringViewType a_rest(a.substr(a_letter_pos + 1));
+  StringViewType b_rest(b.substr(b_letter_pos + 1));
+  return a_rest == b_rest;
+}
+#endif  // defined(FILE_PATH_USES_DRIVE_LETTERS)
+
+bool IsPathAbsolute(StringViewType path) {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  StringType::size_type letter = FindDriveLetter(path);
+  if (letter != StringType::npos) {
+    // Look for a separator right after the drive specification.
+    return path.length() > letter + 1 &&
+           FilePath::IsSeparator(path[letter + 1]);
+  }
+  // Look for a pair of leading separators.
+  return path.length() > 1 && FilePath::IsSeparator(path[0]) &&
+         FilePath::IsSeparator(path[1]);
+#else   // FILE_PATH_USES_DRIVE_LETTERS
+  // Look for a separator in the first position.
+  return path.length() > 0 && FilePath::IsSeparator(path[0]);
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+}
+
+bool AreAllSeparators(const StringType& input) {
+  for (StringType::const_iterator it = input.begin(); it != input.end(); ++it) {
+    if (!FilePath::IsSeparator(*it))
+      return false;
+  }
+
+  return true;
+}
+
+// Find the position of the '.' that separates the extension from the rest
+// of the file name. The position is relative to BaseName(), not value().
+// Returns npos if it can't find an extension.
+StringType::size_type FinalExtensionSeparatorPosition(const StringType& path) {
+  // Special case "." and ".."
+  if (path == FilePath::kCurrentDirectory || path == FilePath::kParentDirectory)
+    return StringType::npos;
+
+  return path.rfind(FilePath::kExtensionSeparator);
+}
+
+// Same as above, but allow a second extension component of up to 4
+// characters when the rightmost extension component is a common double
+// extension (gz, bz2, Z).  For example, foo.tar.gz or foo.tar.Z would have
+// extension components of '.tar.gz' and '.tar.Z' respectively.
+StringType::size_type ExtensionSeparatorPosition(const StringType& path) {
+  const StringType::size_type last_dot = FinalExtensionSeparatorPosition(path);
+
+  // No extension, or the extension is the whole filename.
+  if (last_dot == StringType::npos || last_dot == 0U)
+    return last_dot;
+
+  const StringType::size_type penultimate_dot =
+      path.rfind(FilePath::kExtensionSeparator, last_dot - 1);
+  const StringType::size_type last_separator = path.find_last_of(
+      FilePath::kSeparators, last_dot - 1, FilePath::kSeparatorsLength - 1);
+
+  if (penultimate_dot == StringType::npos ||
+      (last_separator != StringType::npos &&
+       penultimate_dot < last_separator)) {
+    return last_dot;
+  }
+
+  for (size_t i = 0; i < std::size(kCommonDoubleExtensions); ++i) {
+    StringType extension(path, penultimate_dot + 1);
+    if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensions[i]))
+      return penultimate_dot;
+  }
+
+  StringType extension(path, last_dot + 1);
+  for (size_t i = 0; i < std::size(kCommonDoubleExtensionSuffixes); ++i) {
+    if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensionSuffixes[i])) {
+      if ((last_dot - penultimate_dot) <= 5U &&
+          (last_dot - penultimate_dot) > 1U) {
+        return penultimate_dot;
+      }
+    }
+  }
+
+  return last_dot;
+}
+
+// Returns true if path is "", ".", or "..".
+bool IsEmptyOrSpecialCase(const StringType& path) {
+  // Special cases "", ".", and ".."
+  if (path.empty() || path == FilePath::kCurrentDirectory ||
+      path == FilePath::kParentDirectory) {
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace
+
+FilePath::FilePath() = default;
+
+FilePath::FilePath(const FilePath& that) = default;
+FilePath::FilePath(FilePath&& that) noexcept = default;
+
+FilePath::FilePath(StringViewType path) {
+  path_.assign(path);
+  StringType::size_type nul_pos = path_.find(kStringTerminator);
+  if (nul_pos != StringType::npos)
+    path_.erase(nul_pos, StringType::npos);
+}
+
+FilePath::~FilePath() = default;
+
+FilePath& FilePath::operator=(const FilePath& that) = default;
+
+FilePath& FilePath::operator=(FilePath&& that) = default;
+
+bool FilePath::operator==(const FilePath& that) const {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  return EqualDriveLetterCaseInsensitive(this->path_, that.path_);
+#else   // defined(FILE_PATH_USES_DRIVE_LETTERS)
+  return path_ == that.path_;
+#endif  // defined(FILE_PATH_USES_DRIVE_LETTERS)
+}
+
+bool FilePath::operator!=(const FilePath& that) const {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  return !EqualDriveLetterCaseInsensitive(this->path_, that.path_);
+#else   // defined(FILE_PATH_USES_DRIVE_LETTERS)
+  return path_ != that.path_;
+#endif  // defined(FILE_PATH_USES_DRIVE_LETTERS)
+}
+
+// static
+bool FilePath::IsSeparator(CharType character) {
+  for (size_t i = 0; i < kSeparatorsLength - 1; ++i) {
+    if (character == kSeparators[i]) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void FilePath::GetComponents(std::vector<StringType>* components) const {
+  DCHECK(components);
+  if (!components)
+    return;
+  components->clear();
+  if (value().empty())
+    return;
+
+  std::vector<StringType> ret_val;
+  FilePath current = *this;
+  FilePath base;
+
+  // Capture path components.
+  while (current != current.DirName()) {
+    base = current.BaseName();
+    if (!AreAllSeparators(base.value()))
+      ret_val.push_back(base.value());
+    current = current.DirName();
+  }
+
+  // Capture root, if any.
+  base = current.BaseName();
+  if (!base.value().empty() && base.value() != kCurrentDirectory)
+    ret_val.push_back(current.BaseName().value());
+
+  // Capture drive letter, if any.
+  FilePath dir = current.DirName();
+  StringType::size_type letter = FindDriveLetter(dir.value());
+  if (letter != StringType::npos) {
+    ret_val.push_back(StringType(dir.value(), 0, letter + 1));
+  }
+
+  *components = std::vector<StringType>(ret_val.rbegin(), ret_val.rend());
+}
+
+bool FilePath::IsParent(const FilePath& child) const {
+  return AppendRelativePath(child, nullptr);
+}
+
+bool FilePath::AppendRelativePath(const FilePath& child, FilePath* path) const {
+  std::vector<StringType> parent_components;
+  std::vector<StringType> child_components;
+  GetComponents(&parent_components);
+  child.GetComponents(&child_components);
+
+  if (parent_components.empty() ||
+      parent_components.size() >= child_components.size())
+    return false;
+
+  std::vector<StringType>::const_iterator parent_comp =
+      parent_components.begin();
+  std::vector<StringType>::const_iterator child_comp = child_components.begin();
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  // Windows can access case sensitive filesystems, so component
+  // comparisons must be case sensitive, but drive letters are
+  // never case sensitive.
+  if ((FindDriveLetter(*parent_comp) != StringType::npos) &&
+      (FindDriveLetter(*child_comp) != StringType::npos)) {
+    if (!StartsWith(*parent_comp, *child_comp, CompareCase::INSENSITIVE_ASCII))
+      return false;
+    ++parent_comp;
+    ++child_comp;
+  }
+#endif  // defined(FILE_PATH_USES_DRIVE_LETTERS)
+
+  while (parent_comp != parent_components.end()) {
+    if (*parent_comp != *child_comp)
+      return false;
+    ++parent_comp;
+    ++child_comp;
+  }
+
+  if (path != nullptr) {
+    for (; child_comp != child_components.end(); ++child_comp) {
+      *path = path->Append(*child_comp);
+    }
+  }
+  return true;
+}
+
+// libgen's dirname and basename aren't guaranteed to be thread-safe and aren't
+// guaranteed to not modify their input strings, and in fact are implemented
+// differently in this regard on different platforms.  Don't use them, but
+// adhere to their behavior.
+FilePath FilePath::DirName() const {
+  FilePath new_path(path_);
+  new_path.StripTrailingSeparatorsInternal();
+
+  // The drive letter, if any, always needs to remain in the output.  If there
+  // is no drive letter, as will always be the case on platforms which do not
+  // support drive letters, letter will be npos, or -1, so the comparisons and
+  // resizes below using letter will still be valid.
+  StringType::size_type letter = FindDriveLetter(new_path.path_);
+
+  StringType::size_type last_separator = new_path.path_.find_last_of(
+      kSeparators, StringType::npos, kSeparatorsLength - 1);
+  if (last_separator == StringType::npos) {
+    // path_ is in the current directory.
+    new_path.path_.resize(letter + 1);
+  } else if (last_separator == letter + 1) {
+    // path_ is in the root directory.
+    new_path.path_.resize(letter + 2);
+  } else if (last_separator == letter + 2 &&
+             IsSeparator(new_path.path_[letter + 1])) {
+    // path_ is in "//" (possibly with a drive letter); leave the double
+    // separator intact indicating alternate root.
+    new_path.path_.resize(letter + 3);
+  } else if (last_separator != 0) {
+    // path_ is somewhere else, trim the basename.
+    new_path.path_.resize(last_separator);
+  }
+
+  new_path.StripTrailingSeparatorsInternal();
+  if (!new_path.path_.length())
+    new_path.path_ = kCurrentDirectory;
+
+  return new_path;
+}
+
+FilePath FilePath::BaseName() const {
+  FilePath new_path(path_);
+  new_path.StripTrailingSeparatorsInternal();
+
+  // The drive letter, if any, is always stripped.
+  StringType::size_type letter = FindDriveLetter(new_path.path_);
+  if (letter != StringType::npos) {
+    new_path.path_.erase(0, letter + 1);
+  }
+
+  // Keep everything after the final separator, but if the pathname is only
+  // one character and it's a separator, leave it alone.
+  StringType::size_type last_separator = new_path.path_.find_last_of(
+      kSeparators, StringType::npos, kSeparatorsLength - 1);
+  if (last_separator != StringType::npos &&
+      last_separator < new_path.path_.length() - 1) {
+    new_path.path_.erase(0, last_separator + 1);
+  }
+
+  return new_path;
+}
+
+StringType FilePath::Extension() const {
+  FilePath base(BaseName());
+  const StringType::size_type dot = ExtensionSeparatorPosition(base.path_);
+  if (dot == StringType::npos)
+    return StringType();
+
+  return base.path_.substr(dot, StringType::npos);
+}
+
+StringType FilePath::FinalExtension() const {
+  FilePath base(BaseName());
+  const StringType::size_type dot = FinalExtensionSeparatorPosition(base.path_);
+  if (dot == StringType::npos)
+    return StringType();
+
+  return base.path_.substr(dot, StringType::npos);
+}
+
+FilePath FilePath::RemoveExtension() const {
+  if (Extension().empty())
+    return *this;
+
+  const StringType::size_type dot = ExtensionSeparatorPosition(path_);
+  if (dot == StringType::npos)
+    return *this;
+
+  return FilePath(path_.substr(0, dot));
+}
+
+FilePath FilePath::RemoveFinalExtension() const {
+  if (FinalExtension().empty())
+    return *this;
+
+  const StringType::size_type dot = FinalExtensionSeparatorPosition(path_);
+  if (dot == StringType::npos)
+    return *this;
+
+  return FilePath(path_.substr(0, dot));
+}
+
+FilePath FilePath::InsertBeforeExtension(StringViewType suffix) const {
+  if (suffix.empty())
+    return FilePath(path_);
+
+  if (IsEmptyOrSpecialCase(BaseName().value()))
+    return FilePath();
+
+  StringType ext = Extension();
+  StringType ret = RemoveExtension().value();
+  ret.append(suffix);
+  ret.append(ext);
+  return FilePath(ret);
+}
+
+FilePath FilePath::InsertBeforeExtensionASCII(std::string_view suffix) const {
+  DCHECK(IsStringASCII(suffix));
+#if defined(OS_WIN)
+  return InsertBeforeExtension(ASCIIToUTF16(suffix));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  return InsertBeforeExtension(suffix);
+#endif
+}
+
+FilePath FilePath::AddExtension(StringViewType extension) const {
+  if (IsEmptyOrSpecialCase(BaseName().value()))
+    return FilePath();
+
+  // If the new extension is "" or ".", then just return the current FilePath.
+  if (extension.empty() ||
+      (extension.size() == 1 && extension[0] == kExtensionSeparator))
+    return *this;
+
+  StringType str = path_;
+  if (extension[0] != kExtensionSeparator &&
+      *(str.end() - 1) != kExtensionSeparator) {
+    str.append(1, kExtensionSeparator);
+  }
+  str.append(extension);
+  return FilePath(str);
+}
+
+FilePath FilePath::ReplaceExtension(StringViewType extension) const {
+  if (IsEmptyOrSpecialCase(BaseName().value()))
+    return FilePath();
+
+  FilePath no_ext = RemoveExtension();
+  // If the new extension is "" or ".", then just remove the current extension.
+  if (extension.empty() ||
+      (extension.size() == 1 && extension[0] == kExtensionSeparator))
+    return no_ext;
+
+  StringType str = no_ext.value();
+  if (extension[0] != kExtensionSeparator)
+    str.append(1, kExtensionSeparator);
+  str.append(extension);
+  return FilePath(str);
+}
+
+FilePath FilePath::Append(StringViewType component) const {
+  StringViewType appended = component;
+  StringType without_nuls;
+
+  StringType::size_type nul_pos = component.find(kStringTerminator);
+  if (nul_pos != StringViewType::npos) {
+    without_nuls.assign(component.substr(0, nul_pos));
+    appended = StringViewType(without_nuls);
+  }
+
+  DCHECK(!IsPathAbsolute(appended));
+
+  if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
+    // Append normally doesn't do any normalization, but as a special case,
+    // when appending to kCurrentDirectory, just return a new path for the
+    // component argument.  Appending component to kCurrentDirectory would
+    // serve no purpose other than needlessly lengthening the path, and
+    // it's likely in practice to wind up with FilePath objects containing
+    // only kCurrentDirectory when calling DirName on a single relative path
+    // component.
+    return FilePath(appended);
+  }
+
+  FilePath new_path(path_);
+  new_path.StripTrailingSeparatorsInternal();
+
+  // Don't append a separator if the path is empty (indicating the current
+  // directory) or if the path component is empty (indicating nothing to
+  // append).
+  if (!appended.empty() && !new_path.path_.empty()) {
+    // Don't append a separator if the path still ends with a trailing
+    // separator after stripping (indicating the root directory).
+    if (!IsSeparator(new_path.path_.back())) {
+      // Don't append a separator if the path is just a drive letter.
+      if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
+        new_path.path_.append(1, kSeparators[0]);
+      }
+    }
+  }
+
+  new_path.path_.append(appended);
+  return new_path;
+}
+
+FilePath FilePath::Append(const FilePath& component) const {
+  return Append(component.value());
+}
+
+FilePath FilePath::AppendASCII(std::string_view component) const {
+  DCHECK(base::IsStringASCII(component));
+#if defined(OS_WIN)
+  return Append(ASCIIToUTF16(component));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  return Append(component);
+#endif
+}
+
+bool FilePath::IsAbsolute() const {
+  return IsPathAbsolute(path_);
+}
+
+bool FilePath::EndsWithSeparator() const {
+  if (empty())
+    return false;
+  return IsSeparator(path_.back());
+}
+
+FilePath FilePath::AsEndingWithSeparator() const {
+  if (EndsWithSeparator() || path_.empty())
+    return *this;
+
+  StringType path_str;
+  path_str.reserve(path_.length() + 1);  // Only allocate string once.
+
+  path_str = path_;
+  path_str.append(&kSeparators[0], 1);
+  return FilePath(path_str);
+}
+
+FilePath FilePath::StripTrailingSeparators() const {
+  FilePath new_path(path_);
+  new_path.StripTrailingSeparatorsInternal();
+
+  return new_path;
+}
+
+bool FilePath::ReferencesParent() const {
+  if (path_.find(kParentDirectory) == StringType::npos) {
+    // GetComponents is quite expensive, so avoid calling it in the majority
+    // of cases where there isn't a kParentDirectory anywhere in the path.
+    return false;
+  }
+
+  std::vector<StringType> components;
+  GetComponents(&components);
+
+  std::vector<StringType>::const_iterator it = components.begin();
+  for (; it != components.end(); ++it) {
+    const StringType& component = *it;
+    // Windows has odd, undocumented behavior with path components containing
+    // only whitespace and . characters. So, if all we see is . and
+    // whitespace, then we treat any .. sequence as referencing parent.
+    // For simplicity we enforce this on all platforms.
+    if (component.find_first_not_of(FILE_PATH_LITERAL(". \n\r\t")) ==
+            std::string::npos &&
+        component.find(kParentDirectory) != std::string::npos) {
+      return true;
+    }
+  }
+  return false;
+}
+
+#if defined(OS_WIN)
+
+std::u16string FilePath::LossyDisplayName() const {
+  return path_;
+}
+
+std::string FilePath::MaybeAsASCII() const {
+  if (base::IsStringASCII(path_))
+    return UTF16ToASCII(path_);
+  return std::string();
+}
+
+std::string FilePath::As8Bit() const {
+  return UTF16ToUTF8(value());
+}
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+// See file_path.h for a discussion of the encoding of paths on POSIX
+// platforms.  These encoding conversion functions are not quite correct.
+
+std::string FilePath::MaybeAsASCII() const {
+  if (base::IsStringASCII(path_))
+    return path_;
+  return std::string();
+}
+
+std::string FilePath::As8Bit() const {
+  return value();
+}
+
+#endif  // defined(OS_WIN)
+
+void FilePath::StripTrailingSeparatorsInternal() {
+  // If there is no drive letter, start will be 1, which will prevent stripping
+  // the leading separator if there is only one separator.  If there is a drive
+  // letter, start will be set appropriately to prevent stripping the first
+  // separator following the drive letter, if a separator immediately follows
+  // the drive letter.
+  StringType::size_type start = FindDriveLetter(path_) + 2;
+
+  StringType::size_type last_stripped = StringType::npos;
+  for (StringType::size_type pos = path_.length();
+       pos > start && IsSeparator(path_[pos - 1]); --pos) {
+    // If the string only has two separators and they're at the beginning,
+    // don't strip them, unless the string began with more than two separators.
+    if (pos != start + 1 || last_stripped == start + 2 ||
+        !IsSeparator(path_[start - 1])) {
+      path_.resize(pos - 1);
+      last_stripped = pos;
+    }
+  }
+}
+
+FilePath FilePath::NormalizePathSeparators() const {
+  return NormalizePathSeparatorsTo(kSeparators[0]);
+}
+
+FilePath FilePath::NormalizePathSeparatorsTo(CharType separator) const {
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+  DCHECK_NE(kSeparators + kSeparatorsLength,
+            std::find(kSeparators, kSeparators + kSeparatorsLength, separator));
+  StringType copy = path_;
+  for (size_t i = 0; i < kSeparatorsLength; ++i) {
+    std::replace(copy.begin(), copy.end(), kSeparators[i], separator);
+  }
+  return FilePath(copy);
+#else
+  return *this;
+#endif
+}
+
+}  // namespace base
diff --git a/src/base/files/file_path.h b/src/base/files/file_path.h
new file mode 100644 (file)
index 0000000..2359bb6
--- /dev/null
@@ -0,0 +1,394 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// FilePath is a container for pathnames stored in a platform's native string
+// type, providing containers for manipulation in according with the
+// platform's conventions for pathnames.  It supports the following path
+// types:
+//
+//                   POSIX            Windows
+//                   ---------------  ----------------------------------
+// Fundamental type  char[]           char16_t[]
+// Encoding          unspecified*     UTF-16
+// Separator         /                \, tolerant of /
+// Drive letters     no               case-insensitive A-Z followed by :
+// Alternate root    // (surprise!)   \\, for UNC paths
+//
+// * The encoding need not be specified on POSIX systems, although some
+//   POSIX-compliant systems do specify an encoding.  Mac OS X uses UTF-8.
+//   Chrome OS also uses UTF-8.
+//   Linux does not specify an encoding, but in practice, the locale's
+//   character set may be used.
+//
+// For more arcane bits of path trivia, see below.
+//
+// FilePath objects are intended to be used anywhere paths are.  An
+// application may pass FilePath objects around internally, masking the
+// underlying differences between systems, only differing in implementation
+// where interfacing directly with the system.  For example, a single
+// OpenFile(const FilePath &) function may be made available, allowing all
+// callers to operate without regard to the underlying implementation.  On
+// POSIX-like platforms, OpenFile might wrap fopen, and on Windows, it might
+// wrap _wfopen_s, perhaps both by calling file_path.value().c_str().  This
+// allows each platform to pass pathnames around without requiring conversions
+// between encodings, which has an impact on performance, but more imporantly,
+// has an impact on correctness on platforms that do not have well-defined
+// encodings for pathnames.
+//
+// Several methods are available to perform common operations on a FilePath
+// object, such as determining the parent directory (DirName), isolating the
+// final path component (BaseName), and appending a relative pathname string
+// to an existing FilePath object (Append).  These methods are highly
+// recommended over attempting to split and concatenate strings directly.
+// These methods are based purely on string manipulation and knowledge of
+// platform-specific pathname conventions, and do not consult the filesystem
+// at all, making them safe to use without fear of blocking on I/O operations.
+// These methods do not function as mutators but instead return distinct
+// instances of FilePath objects, and are therefore safe to use on const
+// objects.  The objects themselves are safe to share between threads.
+//
+// To aid in initialization of FilePath objects from string literals, a
+// FILE_PATH_LITERAL macro is provided, which accounts for the difference
+// between char[]-based pathnames on POSIX systems and char16_t[]-based
+// pathnames on Windows.
+//
+// As a precaution against premature truncation, paths can't contain NULs.
+//
+// Because a FilePath object should not be instantiated at the global scope,
+// instead, use a FilePath::CharType[] and initialize it with
+// FILE_PATH_LITERAL.  At runtime, a FilePath object can be created from the
+// character array.  Example:
+//
+// | const FilePath::CharType kLogFileName[] = FILE_PATH_LITERAL("log.txt");
+// |
+// | void Function() {
+// |   FilePath log_file_path(kLogFileName);
+// |   [...]
+// | }
+//
+// WARNING: FilePaths should ALWAYS be displayed with LTR directionality, even
+// when the UI language is RTL. This means you always need to pass filepaths
+// through base::i18n::WrapPathWithLTRFormatting() before displaying it in the
+// RTL UI.
+//
+// This is a very common source of bugs, please try to keep this in mind.
+//
+// ARCANE BITS OF PATH TRIVIA
+//
+//  - A double leading slash is actually part of the POSIX standard.  Systems
+//    are allowed to treat // as an alternate root, as Windows does for UNC
+//    (network share) paths.  Most POSIX systems don't do anything special
+//    with two leading slashes, but FilePath handles this case properly
+//    in case it ever comes across such a system.  FilePath needs this support
+//    for Windows UNC paths, anyway.
+//    References:
+//    The Open Group Base Specifications Issue 7, sections 3.267 ("Pathname")
+//    and 4.12 ("Pathname Resolution"), available at:
+//    http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267
+//    http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12
+//
+//  - Windows treats c:\\ the same way it treats \\.  This was intended to
+//    allow older applications that require drive letters to support UNC paths
+//    like \\server\share\path, by permitting c:\\server\share\path as an
+//    equivalent.  Since the OS treats these paths specially, FilePath needs
+//    to do the same.  Since Windows can use either / or \ as the separator,
+//    FilePath treats c://, c:\\, //, and \\ all equivalently.
+//    Reference:
+//    The Old New Thing, "Why is a drive letter permitted in front of UNC
+//    paths (sometimes)?", available at:
+//    http://blogs.msdn.com/oldnewthing/archive/2005/11/22/495740.aspx
+
+#ifndef BASE_FILES_FILE_PATH_H_
+#define BASE_FILES_FILE_PATH_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "util/build_config.h"
+
+// Windows-style drive letter support and pathname separator characters can be
+// enabled and disabled independently, to aid testing.  These #defines are
+// here so that the same setting can be used in both the implementation and
+// in the unit test.
+#if defined(OS_WIN)
+#define FILE_PATH_USES_DRIVE_LETTERS
+#define FILE_PATH_USES_WIN_SEPARATORS
+#endif  // OS_WIN
+
+// To print path names portably use PRIsFP (based on PRIuS and friends from
+// C99 and format_macros.h) like this:
+// base::StringPrintf("Path is %" PRIsFP ".\n", PATH_CSTR(path);
+#if defined(OS_WIN)
+#define PRIsFP "ls"
+#define PATH_CSTR(x) reinterpret_cast<const wchar_t*>(x.value().c_str())
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define PRIsFP "s"
+#define PATH_CSTR(x) (x.value().c_str())
+#endif  // OS_WIN
+
+namespace base {
+
+class Pickle;
+class PickleIterator;
+
+// An abstraction to isolate users from the differences between native
+// pathnames on different platforms.
+class FilePath {
+ public:
+#if defined(OS_WIN)
+  // On Windows, for Unicode-aware applications, native pathnames are char16_t
+  // arrays encoded in UTF-16.
+  typedef std::u16string StringType;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  // On most platforms, native pathnames are char arrays, and the encoding
+  // may or may not be specified.  On Mac OS X, native pathnames are encoded
+  // in UTF-8.
+  typedef std::string StringType;
+#endif  // OS_WIN
+
+  using CharType = StringType::value_type;
+  using StringViewType = std::basic_string_view<CharType>;
+
+  // Null-terminated array of separators used to separate components in
+  // hierarchical paths.  Each character in this array is a valid separator,
+  // but kSeparators[0] is treated as the canonical separator and will be used
+  // when composing pathnames.
+  static const CharType kSeparators[];
+
+  // std::size(kSeparators).
+  static const size_t kSeparatorsLength;
+
+  // A special path component meaning "this directory."
+  static const CharType kCurrentDirectory[];
+
+  // A special path component meaning "the parent directory."
+  static const CharType kParentDirectory[];
+
+  // The character used to identify a file extension.
+  static const CharType kExtensionSeparator;
+
+  FilePath();
+  FilePath(const FilePath& that);
+  explicit FilePath(StringViewType path);
+  ~FilePath();
+  FilePath& operator=(const FilePath& that);
+
+  // Constructs FilePath with the contents of |that|, which is left in valid but
+  // unspecified state.
+  FilePath(FilePath&& that) noexcept;
+  // Replaces the contents with those of |that|, which is left in valid but
+  // unspecified state.
+  FilePath& operator=(FilePath&& that);
+
+  bool operator==(const FilePath& that) const;
+
+  bool operator!=(const FilePath& that) const;
+
+  // Required for some STL containers and operations
+  bool operator<(const FilePath& that) const { return path_ < that.path_; }
+
+  const StringType& value() const { return path_; }
+
+  bool empty() const { return path_.empty(); }
+
+  void clear() { path_.clear(); }
+
+  // Returns true if |character| is in kSeparators.
+  static bool IsSeparator(CharType character);
+
+  // Returns a vector of all of the components of the provided path. It is
+  // equivalent to calling DirName().value() on the path's root component,
+  // and BaseName().value() on each child component.
+  //
+  // To make sure this is lossless so we can differentiate absolute and
+  // relative paths, the root slash will be included even though no other
+  // slashes will be. The precise behavior is:
+  //
+  // Posix:  "/foo/bar"  ->  [ "/", "foo", "bar" ]
+  // Windows:  "C:\foo\bar"  ->  [ "C:", "\\", "foo", "bar" ]
+  void GetComponents(std::vector<FilePath::StringType>* components) const;
+
+  // Returns true if this FilePath is a strict parent of the |child|. Absolute
+  // and relative paths are accepted i.e. is /foo parent to /foo/bar and
+  // is foo parent to foo/bar. Does not convert paths to absolute, follow
+  // symlinks or directory navigation (e.g. ".."). A path is *NOT* its own
+  // parent.
+  bool IsParent(const FilePath& child) const;
+
+  // If IsParent(child) holds, appends to path (if non-NULL) the
+  // relative path to child and returns true.  For example, if parent
+  // holds "/Users/johndoe/Library/Application Support", child holds
+  // "/Users/johndoe/Library/Application Support/Google/Chrome/Default", and
+  // *path holds "/Users/johndoe/Library/Caches", then after
+  // parent.AppendRelativePath(child, path) is called *path will hold
+  // "/Users/johndoe/Library/Caches/Google/Chrome/Default".  Otherwise,
+  // returns false.
+  bool AppendRelativePath(const FilePath& child, FilePath* path) const;
+
+  // Returns a FilePath corresponding to the directory containing the path
+  // named by this object, stripping away the file component.  If this object
+  // only contains one component, returns a FilePath identifying
+  // kCurrentDirectory.  If this object already refers to the root directory,
+  // returns a FilePath identifying the root directory. Please note that this
+  // doesn't resolve directory navigation, e.g. the result for "../a" is "..".
+  FilePath DirName() const WARN_UNUSED_RESULT;
+
+  // Returns a FilePath corresponding to the last path component of this
+  // object, either a file or a directory.  If this object already refers to
+  // the root directory, returns a FilePath identifying the root directory;
+  // this is the only situation in which BaseName will return an absolute path.
+  FilePath BaseName() const WARN_UNUSED_RESULT;
+
+  // Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if
+  // the file has no extension.  If non-empty, Extension() will always start
+  // with precisely one ".".  The following code should always work regardless
+  // of the value of path.  For common double-extensions like .tar.gz and
+  // .user.js, this method returns the combined extension.  For a single
+  // component, use FinalExtension().
+  // new_path = path.RemoveExtension().value().append(path.Extension());
+  // ASSERT(new_path == path.value());
+  // NOTE: this is different from the original file_util implementation which
+  // returned the extension without a leading "." ("jpg" instead of ".jpg")
+  StringType Extension() const WARN_UNUSED_RESULT;
+
+  // Returns the path's file extension, as in Extension(), but will
+  // never return a double extension.
+  //
+  // TODO(davidben): Check all our extension-sensitive code to see if
+  // we can rename this to Extension() and the other to something like
+  // LongExtension(), defaulting to short extensions and leaving the
+  // long "extensions" to logic like base::GetUniquePathNumber().
+  StringType FinalExtension() const WARN_UNUSED_RESULT;
+
+  // Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg"
+  // NOTE: this is slightly different from the similar file_util implementation
+  // which returned simply 'jojo'.
+  FilePath RemoveExtension() const WARN_UNUSED_RESULT;
+
+  // Removes the path's file extension, as in RemoveExtension(), but
+  // ignores double extensions.
+  FilePath RemoveFinalExtension() const WARN_UNUSED_RESULT;
+
+  // Inserts |suffix| after the file name portion of |path| but before the
+  // extension.  Returns "" if BaseName() == "." or "..".
+  // Examples:
+  // path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg"
+  // path == "jojo.jpg"         suffix == " (1)", returns "jojo (1).jpg"
+  // path == "C:\pics\jojo"     suffix == " (1)", returns "C:\pics\jojo (1)"
+  // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)"
+  FilePath InsertBeforeExtension(StringViewType suffix) const
+      WARN_UNUSED_RESULT;
+  FilePath InsertBeforeExtensionASCII(std::string_view suffix) const
+      WARN_UNUSED_RESULT;
+
+  // Adds |extension| to |file_name|. Returns the current FilePath if
+  // |extension| is empty. Returns "" if BaseName() == "." or "..".
+  FilePath AddExtension(StringViewType extension) const WARN_UNUSED_RESULT;
+
+  // Replaces the extension of |file_name| with |extension|.  If |file_name|
+  // does not have an extension, then |extension| is added.  If |extension| is
+  // empty, then the extension is removed from |file_name|.
+  // Returns "" if BaseName() == "." or "..".
+  FilePath ReplaceExtension(StringViewType extension) const WARN_UNUSED_RESULT;
+
+  // Returns a FilePath by appending a separator and the supplied path
+  // component to this object's path.  Append takes care to avoid adding
+  // excessive separators if this object's path already ends with a separator.
+  // If this object's path is kCurrentDirectory, a new FilePath corresponding
+  // only to |component| is returned.  |component| must be a relative path;
+  // it is an error to pass an absolute path.
+  FilePath Append(StringViewType component) const WARN_UNUSED_RESULT;
+  FilePath Append(const FilePath& component) const WARN_UNUSED_RESULT;
+
+  // Although Windows StringType is std::u16string, since the encoding it uses
+  // for paths is well defined, it can handle ASCII path components as well. Mac
+  // uses UTF8, and since ASCII is a subset of that, it works there as well. On
+  // Linux, although it can use any 8-bit encoding for paths, we assume that
+  // ASCII is a valid subset, regardless of the encoding, since many operating
+  // system paths will always be ASCII.
+  FilePath AppendASCII(std::string_view component) const WARN_UNUSED_RESULT;
+
+  // Returns true if this FilePath contains an absolute path.  On Windows, an
+  // absolute path begins with either a drive letter specification followed by
+  // a separator character, or with two separator characters.  On POSIX
+  // platforms, an absolute path begins with a separator character.
+  bool IsAbsolute() const;
+
+  // Returns true if the patch ends with a path separator character.
+  bool EndsWithSeparator() const WARN_UNUSED_RESULT;
+
+  // Returns a copy of this FilePath that ends with a trailing separator. If
+  // the input path is empty, an empty FilePath will be returned.
+  FilePath AsEndingWithSeparator() const WARN_UNUSED_RESULT;
+
+  // Returns a copy of this FilePath that does not end with a trailing
+  // separator.
+  FilePath StripTrailingSeparators() const WARN_UNUSED_RESULT;
+
+  // Returns true if this FilePath contains an attempt to reference a parent
+  // directory (e.g. has a path component that is "..").
+  bool ReferencesParent() const;
+
+  // Return a Unicode human-readable version of this path.
+  // Warning: you can *not*, in general, go from a display name back to a real
+  // path.  Only use this when displaying paths to users, not just when you
+  // want to stuff a std::u16string into some other API.
+  std::u16string LossyDisplayName() const;
+
+  // Return the path as ASCII, or the empty string if the path is not ASCII.
+  // This should only be used for cases where the FilePath is representing a
+  // known-ASCII filename.
+  std::string MaybeAsASCII() const;
+
+  // Return the path as 8-bit. On Linux this isn't guaranteed to be UTF-8.
+  std::string As8Bit() const;
+
+  // Normalize all path separators to backslash on Windows
+  // (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems.
+  FilePath NormalizePathSeparators() const;
+
+  // Normalize all path separattors to given type on Windows
+  // (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems.
+  FilePath NormalizePathSeparatorsTo(CharType separator) const;
+
+ private:
+  // Remove trailing separators from this object.  If the path is absolute, it
+  // will never be stripped any more than to refer to the absolute root
+  // directory, so "////" will become "/", not "".  A leading pair of
+  // separators is never stripped, to support alternate roots.  This is used to
+  // support UNC paths on Windows.
+  void StripTrailingSeparatorsInternal();
+
+  StringType path_;
+};
+
+}  // namespace base
+
+// Macros for string literal initialization of FilePath::CharType[].
+#if defined(OS_WIN)
+#define FILE_PATH_LITERAL(x) u##x
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define FILE_PATH_LITERAL(x) x
+#endif  // OS_WIN
+
+namespace std {
+
+template <>
+struct hash<base::FilePath> {
+  typedef base::FilePath argument_type;
+  typedef std::size_t result_type;
+  result_type operator()(argument_type const& f) const {
+    return hash<base::FilePath::StringType>()(f.value());
+  }
+};
+
+}  // namespace std
+
+#endif  // BASE_FILES_FILE_PATH_H_
diff --git a/src/base/files/file_path_constants.cc b/src/base/files/file_path_constants.cc
new file mode 100644 (file)
index 0000000..9ae0388
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <iterator>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+
+namespace base {
+
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("\\/");
+#else   // FILE_PATH_USES_WIN_SEPARATORS
+const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/");
+#endif  // FILE_PATH_USES_WIN_SEPARATORS
+
+const size_t FilePath::kSeparatorsLength = std::size(kSeparators);
+
+const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL(".");
+const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL("..");
+
+const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.');
+
+}  // namespace base
diff --git a/src/base/files/file_posix.cc b/src/base/files/file_posix.cc
new file mode 100644 (file)
index 0000000..349cbfe
--- /dev/null
@@ -0,0 +1,392 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+namespace base {
+
+// Make sure our Whence mappings match the system headers.
+static_assert(File::FROM_BEGIN == SEEK_SET && File::FROM_CURRENT == SEEK_CUR &&
+                  File::FROM_END == SEEK_END,
+              "whence mapping must match the system headers");
+
+namespace {
+
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+int CallFstat(int fd, stat_wrapper_t* sb) {
+  return fstat(fd, sb);
+}
+#else
+int CallFstat(int fd, stat_wrapper_t* sb) {
+  return fstat64(fd, sb);
+}
+#endif
+
+// Some systems don't provide the following system calls, so either simulate
+// them or wrap them in order to minimize the number of #ifdef's in this file.
+#if !defined(OS_AIX)
+bool IsOpenAppend(PlatformFile file) {
+  return (fcntl(file, F_GETFL) & O_APPEND) != 0;
+}
+
+int CallFtruncate(PlatformFile file, int64_t length) {
+  return HANDLE_EINTR(ftruncate(file, length));
+}
+
+#if !defined(OS_FUCHSIA)
+File::Error CallFcntlFlock(PlatformFile file, bool do_lock) {
+  struct flock lock;
+  lock.l_type = do_lock ? F_WRLCK : F_UNLCK;
+  lock.l_whence = SEEK_SET;
+  lock.l_start = 0;
+  lock.l_len = 0;  // Lock entire file.
+  if (HANDLE_EINTR(fcntl(file, F_SETLK, &lock)) == -1)
+    return File::GetLastFileError();
+  return File::FILE_OK;
+}
+#endif
+
+#else   // !defined(OS_AIX)
+
+bool IsOpenAppend(PlatformFile file) {
+  // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX
+  // standard and always appends if the file is opened with O_APPEND, just
+  // return false here.
+  return false;
+}
+
+int CallFtruncate(PlatformFile file, int64_t length) {
+  NOTIMPLEMENTED();  // NaCl doesn't implement ftruncate.
+  return 0;
+}
+
+File::Error CallFcntlFlock(PlatformFile file, bool do_lock) {
+  NOTIMPLEMENTED();  // NaCl doesn't implement flock struct.
+  return File::FILE_ERROR_INVALID_OPERATION;
+}
+#endif  // defined(OS_AIX)
+
+}  // namespace
+
+void File::Info::FromStat(const stat_wrapper_t& stat_info) {
+  is_directory = S_ISDIR(stat_info.st_mode);
+  is_symbolic_link = S_ISLNK(stat_info.st_mode);
+  size = stat_info.st_size;
+
+#if defined(OS_MACOSX)
+  time_t last_modified_sec = stat_info.st_mtimespec.tv_sec;
+  int64_t last_modified_nsec = stat_info.st_mtimespec.tv_nsec;
+  time_t last_accessed_sec = stat_info.st_atimespec.tv_sec;
+  int64_t last_accessed_nsec = stat_info.st_atimespec.tv_nsec;
+  time_t creation_time_sec = stat_info.st_ctimespec.tv_sec;
+  int64_t creation_time_nsec = stat_info.st_ctimespec.tv_nsec;
+#elif defined(OS_AIX)
+  time_t last_modified_sec = stat_info.st_mtime;
+  int64_t last_modified_nsec = 0;
+  time_t last_accessed_sec = stat_info.st_atime;
+  int64_t last_accessed_nsec = 0;
+  time_t creation_time_sec = stat_info.st_ctime;
+  int64_t creation_time_nsec = 0;
+#elif defined(OS_POSIX)
+  time_t last_modified_sec = stat_info.st_mtim.tv_sec;
+  int64_t last_modified_nsec = stat_info.st_mtim.tv_nsec;
+  time_t last_accessed_sec = stat_info.st_atim.tv_sec;
+  int64_t last_accessed_nsec = stat_info.st_atim.tv_nsec;
+  time_t creation_time_sec = stat_info.st_ctim.tv_sec;
+  int64_t creation_time_nsec = stat_info.st_ctim.tv_nsec;
+#else
+#error
+#endif
+
+  constexpr uint64_t kNano = 1'000'000'000;
+  last_modified = last_modified_sec * kNano + last_modified_nsec;
+  last_accessed = last_accessed_sec * kNano + last_accessed_nsec;
+  creation_time = creation_time_sec * kNano + creation_time_nsec;
+}
+
+bool File::IsValid() const {
+  return file_.is_valid();
+}
+
+PlatformFile File::GetPlatformFile() const {
+  return file_.get();
+}
+
+PlatformFile File::TakePlatformFile() {
+  return file_.release();
+}
+
+void File::Close() {
+  if (!IsValid())
+    return;
+
+  file_.reset();
+}
+
+int64_t File::Seek(Whence whence, int64_t offset) {
+  DCHECK(IsValid());
+
+  static_assert(sizeof(int64_t) == sizeof(off_t), "off_t must be 64 bits");
+  return lseek(file_.get(), static_cast<off_t>(offset),
+               static_cast<int>(whence));
+}
+
+int File::Read(int64_t offset, char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  int bytes_read = 0;
+  int rv;
+  do {
+    rv = HANDLE_EINTR(pread(file_.get(), data + bytes_read, size - bytes_read,
+                            offset + bytes_read));
+    if (rv <= 0)
+      break;
+
+    bytes_read += rv;
+  } while (bytes_read < size);
+
+  return bytes_read ? bytes_read : rv;
+}
+
+int File::ReadAtCurrentPos(char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  int bytes_read = 0;
+  int rv;
+  do {
+    rv = HANDLE_EINTR(read(file_.get(), data + bytes_read, size - bytes_read));
+    if (rv <= 0)
+      break;
+
+    bytes_read += rv;
+  } while (bytes_read < size);
+
+  return bytes_read ? bytes_read : rv;
+}
+
+int File::ReadNoBestEffort(int64_t offset, char* data, int size) {
+  DCHECK(IsValid());
+  return HANDLE_EINTR(pread(file_.get(), data, size, offset));
+}
+
+int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  return HANDLE_EINTR(read(file_.get(), data, size));
+}
+
+int File::Write(int64_t offset, const char* data, int size) {
+  if (IsOpenAppend(file_.get()))
+    return WriteAtCurrentPos(data, size);
+
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  int bytes_written = 0;
+  int rv;
+  do {
+    rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written,
+                             size - bytes_written, offset + bytes_written));
+    if (rv <= 0)
+      break;
+
+    bytes_written += rv;
+  } while (bytes_written < size);
+
+  return bytes_written ? bytes_written : rv;
+}
+
+int File::WriteAtCurrentPos(const char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  int bytes_written = 0;
+  int rv;
+  do {
+    rv = HANDLE_EINTR(
+        write(file_.get(), data + bytes_written, size - bytes_written));
+    if (rv <= 0)
+      break;
+
+    bytes_written += rv;
+  } while (bytes_written < size);
+
+  return bytes_written ? bytes_written : rv;
+}
+
+int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  return HANDLE_EINTR(write(file_.get(), data, size));
+}
+
+int64_t File::GetLength() {
+  DCHECK(IsValid());
+
+  stat_wrapper_t file_info;
+  if (CallFstat(file_.get(), &file_info))
+    return -1;
+
+  return file_info.st_size;
+}
+
+bool File::SetLength(int64_t length) {
+  DCHECK(IsValid());
+
+  return !CallFtruncate(file_.get(), length);
+}
+
+bool File::GetInfo(Info* info) {
+  DCHECK(IsValid());
+
+  stat_wrapper_t file_info;
+  if (CallFstat(file_.get(), &file_info))
+    return false;
+
+  info->FromStat(file_info);
+  return true;
+}
+
+#if !defined(OS_FUCHSIA)
+File::Error File::Lock() {
+  return CallFcntlFlock(file_.get(), true);
+}
+
+File::Error File::Unlock() {
+  return CallFcntlFlock(file_.get(), false);
+}
+#endif
+
+File File::Duplicate() const {
+  if (!IsValid())
+    return File();
+
+  PlatformFile other_fd = HANDLE_EINTR(dup(GetPlatformFile()));
+  if (other_fd == -1)
+    return File(File::GetLastFileError());
+
+  File other(other_fd);
+  return other;
+}
+
+// Static.
+File::Error File::OSErrorToFileError(int saved_errno) {
+  switch (saved_errno) {
+    case EACCES:
+    case EISDIR:
+    case EROFS:
+    case EPERM:
+      return FILE_ERROR_ACCESS_DENIED;
+    case EBUSY:
+    case ETXTBSY:
+      return FILE_ERROR_IN_USE;
+    case EEXIST:
+      return FILE_ERROR_EXISTS;
+    case EIO:
+      return FILE_ERROR_IO;
+    case ENOENT:
+      return FILE_ERROR_NOT_FOUND;
+    case ENFILE:  // fallthrough
+    case EMFILE:
+      return FILE_ERROR_TOO_MANY_OPENED;
+    case ENOMEM:
+      return FILE_ERROR_NO_MEMORY;
+    case ENOSPC:
+      return FILE_ERROR_NO_SPACE;
+    case ENOTDIR:
+      return FILE_ERROR_NOT_A_DIRECTORY;
+    default:
+      // This function should only be called for errors.
+      DCHECK_NE(0, saved_errno);
+      return FILE_ERROR_FAILED;
+  }
+}
+
+void File::DoInitialize(const FilePath& path, uint32_t flags) {
+  DCHECK(!IsValid());
+
+  int open_flags = 0;
+  created_ = false;
+
+  if (flags & FLAG_CREATE_ALWAYS) {
+    DCHECK(!open_flags);
+    DCHECK(flags & FLAG_WRITE);
+    open_flags = O_CREAT | O_TRUNC;
+  }
+
+  if (!open_flags && !(flags & FLAG_OPEN)) {
+    NOTREACHED();
+    errno = EOPNOTSUPP;
+    error_details_ = FILE_ERROR_FAILED;
+    return;
+  }
+
+  if (flags & FLAG_WRITE && flags & FLAG_READ) {
+    open_flags |= O_RDWR;
+  } else if (flags & FLAG_WRITE) {
+    open_flags |= O_WRONLY;
+  } else if (!(flags & FLAG_READ)) {
+    NOTREACHED();
+  }
+
+  static_assert(O_RDONLY == 0, "O_RDONLY must equal zero");
+
+  int mode = S_IRUSR | S_IWUSR;
+  int descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode));
+
+  if (descriptor < 0) {
+    error_details_ = File::GetLastFileError();
+    return;
+  }
+
+  if (flags & FLAG_CREATE_ALWAYS)
+    created_ = true;
+
+  error_details_ = FILE_OK;
+  file_.reset(descriptor);
+}
+
+bool File::Flush() {
+  DCHECK(IsValid());
+
+#if defined(OS_LINUX)
+  return !HANDLE_EINTR(fdatasync(file_.get()));
+#else
+  return !HANDLE_EINTR(fsync(file_.get()));
+#endif
+}
+
+void File::SetPlatformFile(PlatformFile file) {
+  DCHECK(!file_.is_valid());
+  file_.reset(file);
+}
+
+// static
+File::Error File::GetLastFileError() {
+  return base::File::OSErrorToFileError(errno);
+}
+
+}  // namespace base
diff --git a/src/base/files/file_util.cc b/src/base/files/file_util.cc
new file mode 100644 (file)
index 0000000..c68fe02
--- /dev/null
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+
+#if defined(OS_WIN)
+#include <io.h>
+#endif
+#include <stdio.h>
+
+#include <fstream>
+#include <limits>
+#include <string_view>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+namespace base {
+
+namespace {
+
+// The maximum number of 'uniquified' files we will try to create.
+// This is used when the filename we're trying to download is already in use,
+// so we create a new unique filename by appending " (nnn)" before the
+// extension, where 1 <= nnn <= kMaxUniqueFiles.
+// Also used by code that cleans up said files.
+static const int kMaxUniqueFiles = 100;
+
+}  // namespace
+
+int64_t ComputeDirectorySize(const FilePath& root_path) {
+  int64_t running_size = 0;
+  FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
+  while (!file_iter.Next().empty())
+    running_size += file_iter.GetInfo().GetSize();
+  return running_size;
+}
+
+bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
+  // We open the file in binary format even if they are text files because
+  // we are just comparing that bytes are exactly same in both files and not
+  // doing anything smart with text formatting.
+  std::ifstream file1(filename1.As8Bit().c_str(),
+                      std::ios::in | std::ios::binary);
+  std::ifstream file2(filename2.As8Bit().c_str(),
+                      std::ios::in | std::ios::binary);
+
+  // Even if both files aren't openable (and thus, in some sense, "equal"),
+  // any unusable file yields a result of "false".
+  if (!file1.is_open() || !file2.is_open())
+    return false;
+
+  const int BUFFER_SIZE = 2056;
+  char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
+  do {
+    file1.read(buffer1, BUFFER_SIZE);
+    file2.read(buffer2, BUFFER_SIZE);
+
+    if ((file1.eof() != file2.eof()) || (file1.gcount() != file2.gcount()) ||
+        (memcmp(buffer1, buffer2, static_cast<size_t>(file1.gcount())))) {
+      file1.close();
+      file2.close();
+      return false;
+    }
+  } while (!file1.eof() || !file2.eof());
+
+  file1.close();
+  file2.close();
+  return true;
+}
+
+bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
+  std::ifstream file1(filename1.As8Bit().c_str(), std::ios::in);
+  std::ifstream file2(filename2.As8Bit().c_str(), std::ios::in);
+
+  // Even if both files aren't openable (and thus, in some sense, "equal"),
+  // any unusable file yields a result of "false".
+  if (!file1.is_open() || !file2.is_open())
+    return false;
+
+  do {
+    std::string line1, line2;
+    getline(file1, line1);
+    getline(file2, line2);
+
+    // Check for mismatched EOF states, or any error state.
+    if ((file1.eof() != file2.eof()) || file1.bad() || file2.bad()) {
+      return false;
+    }
+
+    // Trim all '\r' and '\n' characters from the end of the line.
+    std::string::size_type end1 = line1.find_last_not_of("\r\n");
+    if (end1 == std::string::npos)
+      line1.clear();
+    else if (end1 + 1 < line1.length())
+      line1.erase(end1 + 1);
+
+    std::string::size_type end2 = line2.find_last_not_of("\r\n");
+    if (end2 == std::string::npos)
+      line2.clear();
+    else if (end2 + 1 < line2.length())
+      line2.erase(end2 + 1);
+
+    if (line1 != line2)
+      return false;
+  } while (!file1.eof() || !file2.eof());
+
+  return true;
+}
+
+bool ReadFileToStringWithMaxSize(const FilePath& path,
+                                 std::string* contents,
+                                 size_t max_size) {
+  if (contents)
+    contents->clear();
+  if (path.ReferencesParent())
+    return false;
+  FILE* file = OpenFile(path, "rb");
+  if (!file) {
+    return false;
+  }
+
+  // Many files supplied in |path| have incorrect size (proc files etc).
+  // Hence, the file is read sequentially as opposed to a one-shot read, using
+  // file size as a hint for chunk size if available.
+  constexpr int64_t kDefaultChunkSize = 1 << 16;
+  int64_t chunk_size;
+  if (!GetFileSize(path, &chunk_size) || chunk_size <= 0)
+    chunk_size = kDefaultChunkSize - 1;
+  // We need to attempt to read at EOF for feof flag to be set so here we
+  // use |chunk_size| + 1.
+  chunk_size = std::min<uint64_t>(chunk_size, max_size) + 1;
+  size_t bytes_read_this_pass;
+  size_t bytes_read_so_far = 0;
+  bool read_status = true;
+  std::string local_contents;
+  local_contents.resize(chunk_size);
+
+  while ((bytes_read_this_pass = fread(&local_contents[bytes_read_so_far], 1,
+                                       chunk_size, file)) > 0) {
+    if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
+      // Read more than max_size bytes, bail out.
+      bytes_read_so_far = max_size;
+      read_status = false;
+      break;
+    }
+    // In case EOF was not reached, iterate again but revert to the default
+    // chunk size.
+    if (bytes_read_so_far == 0)
+      chunk_size = kDefaultChunkSize;
+
+    bytes_read_so_far += bytes_read_this_pass;
+    // Last fread syscall (after EOF) can be avoided via feof, which is just a
+    // flag check.
+    if (feof(file))
+      break;
+    local_contents.resize(bytes_read_so_far + chunk_size);
+  }
+  read_status = read_status && !ferror(file);
+  CloseFile(file);
+  if (contents) {
+    contents->swap(local_contents);
+    contents->resize(bytes_read_so_far);
+  }
+
+  return read_status;
+}
+
+bool ReadFileToString(const FilePath& path, std::string* contents) {
+  return ReadFileToStringWithMaxSize(path, contents,
+                                     std::numeric_limits<size_t>::max());
+}
+
+bool IsDirectoryEmpty(const FilePath& dir_path) {
+  FileEnumerator files(dir_path, false,
+                       FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
+  if (files.Next().empty())
+    return true;
+  return false;
+}
+
+bool CreateDirectory(const FilePath& full_path) {
+  return CreateDirectoryAndGetError(full_path, nullptr);
+}
+
+bool GetFileSize(const FilePath& file_path, int64_t* file_size) {
+  File::Info info;
+  if (!GetFileInfo(file_path, &info))
+    return false;
+  *file_size = info.size;
+  return true;
+}
+
+bool CloseFile(FILE* file) {
+  if (file == nullptr)
+    return true;
+  return fclose(file) == 0;
+}
+
+bool TruncateFile(FILE* file) {
+  if (file == nullptr)
+    return false;
+  long current_offset = ftell(file);
+  if (current_offset == -1)
+    return false;
+#if defined(OS_WIN)
+  int fd = _fileno(file);
+  if (_chsize(fd, current_offset) != 0)
+    return false;
+#else
+  int fd = fileno(file);
+  if (ftruncate(fd, current_offset) != 0)
+    return false;
+#endif
+  return true;
+}
+
+int GetUniquePathNumber(const FilePath& path,
+                        const FilePath::StringType& suffix) {
+  bool have_suffix = !suffix.empty();
+  if (!PathExists(path) &&
+      (!have_suffix || !PathExists(FilePath(path.value() + suffix)))) {
+    return 0;
+  }
+
+  FilePath new_path;
+  for (int count = 1; count <= kMaxUniqueFiles; ++count) {
+    new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count));
+    if (!PathExists(new_path) &&
+        (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) {
+      return count;
+    }
+  }
+
+  return -1;
+}
+
+}  // namespace base
diff --git a/src/base/files/file_util.h b/src/base/files/file_util.h
new file mode 100644 (file)
index 0000000..b44129d
--- /dev/null
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains utility functions for dealing with the local
+// filesystem.
+
+#ifndef BASE_FILES_FILE_UTIL_H_
+#define BASE_FILES_FILE_UTIL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#endif
+
+namespace base {
+
+class Environment;
+
+//-----------------------------------------------------------------------------
+// Functions that involve filesystem access or modification:
+
+// Returns an absolute version of a relative path. Returns an empty path on
+// error. On POSIX, this function fails if the path does not exist. This
+// function can result in I/O so it can be slow.
+FilePath MakeAbsoluteFilePath(const FilePath& input);
+
+// Returns the total number of bytes used by all the files under |root_path|.
+// If the path does not exist the function returns 0.
+//
+// This function is implemented using the FileEnumerator class so it is not
+// particularly speedy in any platform.
+int64_t ComputeDirectorySize(const FilePath& root_path);
+
+// Deletes the given path, whether it's a file or a directory.
+// If it's a directory, it's perfectly happy to delete all of the
+// directory's contents.  Passing true to recursive deletes
+// subdirectories and their contents as well.
+// Returns true if successful, false otherwise. It is considered successful
+// to attempt to delete a file that does not exist.
+//
+// In posix environment and if |path| is a symbolic link, this deletes only
+// the symlink. (even if the symlink points to a non-existent file)
+//
+// WARNING: USING THIS WITH recursive==true IS EQUIVALENT
+//          TO "rm -rf", SO USE WITH CAUTION.
+bool DeleteFile(const FilePath& path, bool recursive);
+
+#if defined(OS_WIN)
+// Schedules to delete the given path, whether it's a file or a directory, until
+// the operating system is restarted.
+// Note:
+// 1) The file/directory to be deleted should exist in a temp folder.
+// 2) The directory to be deleted must be empty.
+bool DeleteFileAfterReboot(const FilePath& path);
+#endif
+
+// Renames file |from_path| to |to_path|. Both paths must be on the same
+// volume, or the function will fail. Destination file will be created
+// if it doesn't exist. Prefer this function over Move when dealing with
+// temporary files. On Windows it preserves attributes of the target file.
+// Returns true on success, leaving *error unchanged.
+// Returns false on failure and sets *error appropriately, if it is non-NULL.
+bool ReplaceFile(const FilePath& from_path,
+                 const FilePath& to_path,
+                 File::Error* error);
+
+// Returns true if the given path exists on the local filesystem,
+// false otherwise.
+bool PathExists(const FilePath& path);
+
+// Returns true if the given path is writable by the user, false otherwise.
+bool PathIsWritable(const FilePath& path);
+
+// Returns true if the given path exists and is a directory, false otherwise.
+bool DirectoryExists(const FilePath& path);
+
+// Returns true if the contents of the two files given are equal, false
+// otherwise.  If either file can't be read, returns false.
+bool ContentsEqual(const FilePath& filename1, const FilePath& filename2);
+
+// Returns true if the contents of the two text files given are equal, false
+// otherwise.  This routine treats "\r\n" and "\n" as equivalent.
+bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2);
+
+// Reads the file at |path| into |contents| and returns true on success and
+// false on error.  For security reasons, a |path| containing path traversal
+// components ('..') is treated as a read error and |contents| is set to empty.
+// In case of I/O error, |contents| holds the data that could be read from the
+// file before the error occurred.
+// |contents| may be NULL, in which case this function is useful for its side
+// effect of priming the disk cache (could be used for unit tests).
+bool ReadFileToString(const FilePath& path, std::string* contents);
+
+// Reads the file at |path| into |contents| and returns true on success and
+// false on error.  For security reasons, a |path| containing path traversal
+// components ('..') is treated as a read error and |contents| is set to empty.
+// In case of I/O error, |contents| holds the data that could be read from the
+// file before the error occurred.  When the file size exceeds |max_size|, the
+// function returns false with |contents| holding the file truncated to
+// |max_size|.
+// |contents| may be NULL, in which case this function is useful for its side
+// effect of priming the disk cache (could be used for unit tests).
+bool ReadFileToStringWithMaxSize(const FilePath& path,
+                                 std::string* contents,
+                                 size_t max_size);
+
+#if defined(OS_POSIX)
+
+// Creates a symbolic link at |symlink| pointing to |target|.  Returns
+// false on failure.
+bool CreateSymbolicLink(const FilePath& target, const FilePath& symlink);
+
+// Reads the given |symlink| and returns where it points to in |target|.
+// Returns false upon failure.
+bool ReadSymbolicLink(const FilePath& symlink, FilePath* target);
+
+// Bits and masks of the file permission.
+enum FilePermissionBits {
+  FILE_PERMISSION_MASK = S_IRWXU | S_IRWXG | S_IRWXO,
+  FILE_PERMISSION_USER_MASK = S_IRWXU,
+  FILE_PERMISSION_GROUP_MASK = S_IRWXG,
+  FILE_PERMISSION_OTHERS_MASK = S_IRWXO,
+
+  FILE_PERMISSION_READ_BY_USER = S_IRUSR,
+  FILE_PERMISSION_WRITE_BY_USER = S_IWUSR,
+  FILE_PERMISSION_EXECUTE_BY_USER = S_IXUSR,
+  FILE_PERMISSION_READ_BY_GROUP = S_IRGRP,
+  FILE_PERMISSION_WRITE_BY_GROUP = S_IWGRP,
+  FILE_PERMISSION_EXECUTE_BY_GROUP = S_IXGRP,
+  FILE_PERMISSION_READ_BY_OTHERS = S_IROTH,
+  FILE_PERMISSION_WRITE_BY_OTHERS = S_IWOTH,
+  FILE_PERMISSION_EXECUTE_BY_OTHERS = S_IXOTH,
+};
+
+// Reads the permission of the given |path|, storing the file permission
+// bits in |mode|. If |path| is symbolic link, |mode| is the permission of
+// a file which the symlink points to.
+bool GetPosixFilePermissions(const FilePath& path, int* mode);
+// Sets the permission of the given |path|. If |path| is symbolic link, sets
+// the permission of a file which the symlink points to.
+bool SetPosixFilePermissions(const FilePath& path, int mode);
+
+// Returns true iff |executable| can be found in any directory specified by the
+// environment variable in |env|.
+bool ExecutableExistsInPath(Environment* env,
+                            const FilePath::StringType& executable);
+
+#endif  // OS_POSIX
+
+// Returns true if the given directory is empty
+bool IsDirectoryEmpty(const FilePath& dir_path);
+
+// Get the temporary directory provided by the system.
+bool GetTempDir(FilePath* path);
+
+// Create a new directory. If prefix is provided, the new directory name is in
+// the format of prefixyyyy.
+// NOTE: prefix is ignored in the POSIX implementation.
+// If success, return true and output the full path of the directory created.
+bool CreateNewTempDirectory(const FilePath::StringType& prefix,
+                            FilePath* new_temp_path);
+
+// Create a directory within another directory.
+// Extra characters will be appended to |prefix| to ensure that the
+// new directory does not have the same name as an existing directory.
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir);
+
+// Creates a directory, as well as creating any parent directories, if they
+// don't exist. Returns 'true' on successful creation, or if the directory
+// already exists.  The directory is only readable by the current user.
+// Returns true on success, leaving *error unchanged.
+// Returns false on failure and sets *error appropriately, if it is non-NULL.
+bool CreateDirectoryAndGetError(const FilePath& full_path, File::Error* error);
+
+// Backward-compatible convenience method for the above.
+bool CreateDirectory(const FilePath& full_path);
+
+// Returns the file size. Returns true on success.
+bool GetFileSize(const FilePath& file_path, int64_t* file_size);
+
+// Sets |real_path| to |path| with symbolic links and junctions expanded.
+// On windows, make sure the path starts with a lettered drive.
+// |path| must reference a file.  Function will fail if |path| points to
+// a directory or to a nonexistent path.  On windows, this function will
+// fail if |path| is a junction or symlink that points to an empty file,
+// or if |real_path| would be longer than MAX_PATH characters.
+bool NormalizeFilePath(const FilePath& path, FilePath* real_path);
+
+#if defined(OS_WIN)
+
+// Given a path in NT native form ("\Device\HarddiskVolumeXX\..."),
+// return in |drive_letter_path| the equivalent path that starts with
+// a drive letter ("C:\...").  Return false if no such path exists.
+bool DevicePathToDriveLetterPath(const FilePath& device_path,
+                                 FilePath* drive_letter_path);
+
+// Given an existing file in |path|, set |real_path| to the path
+// in native NT format, of the form "\Device\HarddiskVolumeXX\..".
+// Returns false if the path can not be found. Empty files cannot
+// be resolved with this function.
+bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path);
+#endif
+
+// This function will return if the given file is a symlink or not.
+bool IsLink(const FilePath& file_path);
+
+// Returns information about the given file path.
+bool GetFileInfo(const FilePath& file_path, File::Info* info);
+
+// Wrapper for fopen-like calls. Returns non-NULL FILE* on success. The
+// underlying file descriptor (POSIX) or handle (Windows) is unconditionally
+// configured to not be propagated to child processes.
+FILE* OpenFile(const FilePath& filename, const char* mode);
+
+// Closes file opened by OpenFile. Returns true on success.
+bool CloseFile(FILE* file);
+
+// Associates a standard FILE stream with an existing File. Note that this
+// functions take ownership of the existing File.
+FILE* FileToFILE(File file, const char* mode);
+
+// Truncates an open file to end at the location of the current file pointer.
+// This is a cross-platform analog to Windows' SetEndOfFile() function.
+bool TruncateFile(FILE* file);
+
+// Reads at most the given number of bytes from the file into the buffer.
+// Returns the number of read bytes, or -1 on error.
+int ReadFile(const FilePath& filename, char* data, int max_size);
+
+// Writes the given buffer into the file, overwriting any data that was
+// previously there.  Returns the number of bytes written, or -1 on error.
+int WriteFile(const FilePath& filename, const char* data, int size);
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+// Appends |data| to |fd|. Does not close |fd| when done.  Returns true iff
+// |size| bytes of |data| were written to |fd|.
+bool WriteFileDescriptor(const int fd, const char* data, int size);
+#endif
+
+// Appends |data| to |filename|.  Returns true iff |size| bytes of |data| were
+// written to |filename|.
+bool AppendToFile(const FilePath& filename, const char* data, int size);
+
+// Gets the current working directory for the process.
+bool GetCurrentDirectory(FilePath* path);
+
+// Sets the current working directory for the process.
+bool SetCurrentDirectory(const FilePath& path);
+
+// Attempts to find a number that can be appended to the |path| to make it
+// unique. If |path| does not exist, 0 is returned.  If it fails to find such
+// a number, -1 is returned. If |suffix| is not empty, also checks the
+// existence of it with the given suffix.
+int GetUniquePathNumber(const FilePath& path,
+                        const FilePath::StringType& suffix);
+
+// Sets the given |fd| to non-blocking mode.
+// Returns true if it was able to set it in the non-blocking mode, otherwise
+// false.
+bool SetNonBlocking(int fd);
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+// Creates a non-blocking, close-on-exec pipe.
+// This creates a non-blocking pipe that is not intended to be shared with any
+// child process. This will be done atomically if the operating system supports
+// it. Returns true if it was able to create the pipe, otherwise false.
+bool CreateLocalNonBlockingPipe(int fds[2]);
+
+// Sets the given |fd| to close-on-exec mode.
+// Returns true if it was able to set it in the close-on-exec mode, otherwise
+// false.
+bool SetCloseOnExec(int fd);
+
+// Test that |path| can only be changed by a given user and members of
+// a given set of groups.
+// Specifically, test that all parts of |path| under (and including) |base|:
+// * Exist.
+// * Are owned by a specific user.
+// * Are not writable by all users.
+// * Are owned by a member of a given set of groups, or are not writable by
+//   their group.
+// * Are not symbolic links.
+// This is useful for checking that a config file is administrator-controlled.
+// |base| must contain |path|.
+bool VerifyPathControlledByUser(const base::FilePath& base,
+                                const base::FilePath& path,
+                                uid_t owner_uid,
+                                const std::set<gid_t>& group_gids);
+#endif  // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Is |path| writable only by a user with administrator privileges?
+// This function uses Mac OS conventions.  The super user is assumed to have
+// uid 0, and the administrator group is assumed to be named "admin".
+// Testing that |path|, and every parent directory including the root of
+// the filesystem, are owned by the superuser, controlled by the group
+// "admin", are not writable by all users, and contain no symbolic links.
+// Will return false if |path| does not exist.
+bool VerifyPathControlledByAdmin(const base::FilePath& path);
+#endif  // defined(OS_MACOSX) && !defined(OS_IOS)
+
+// Returns the maximum length of path component on the volume containing
+// the directory |path|, in the number of FilePath::CharType, or -1 on failure.
+int GetMaximumPathComponentLength(const base::FilePath& path);
+
+#if defined(OS_LINUX) || defined(OS_AIX) || defined(OS_BSD)
+// Broad categories of file systems as returned by statfs() on Linux.
+enum FileSystemType {
+  FILE_SYSTEM_UNKNOWN,   // statfs failed.
+  FILE_SYSTEM_0,         // statfs.f_type == 0 means unknown, may indicate AFS.
+  FILE_SYSTEM_ORDINARY,  // on-disk filesystem like ext2
+  FILE_SYSTEM_NFS,
+  FILE_SYSTEM_SMB,
+  FILE_SYSTEM_CODA,
+  FILE_SYSTEM_MEMORY,  // in-memory file system
+  FILE_SYSTEM_CGROUP,  // cgroup control.
+  FILE_SYSTEM_OTHER,   // any other value.
+  FILE_SYSTEM_TYPE_COUNT
+};
+
+// Attempts determine the FileSystemType for |path|.
+// Returns false if |path| doesn't exist.
+bool GetFileSystemType(const FilePath& path, FileSystemType* type);
+#endif
+
+}  // namespace base
+
+#endif  // BASE_FILES_FILE_UTIL_H_
diff --git a/src/base/files/file_util_linux.cc b/src/base/files/file_util_linux.cc
new file mode 100644 (file)
index 0000000..b230fd9
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+
+#include <errno.h>
+#include <linux/magic.h>
+#include <sys/vfs.h>
+
+#include "base/files/file_path.h"
+
+namespace base {
+
+bool GetFileSystemType(const FilePath& path, FileSystemType* type) {
+  struct statfs statfs_buf;
+  if (statfs(path.value().c_str(), &statfs_buf) < 0) {
+    if (errno == ENOENT)
+      return false;
+    *type = FILE_SYSTEM_UNKNOWN;
+    return true;
+  }
+
+  // Not all possible |statfs_buf.f_type| values are in linux/magic.h.
+  // Missing values are copied from the statfs man page.
+  switch (statfs_buf.f_type) {
+    case 0:
+      *type = FILE_SYSTEM_0;
+      break;
+    case EXT2_SUPER_MAGIC:  // Also ext3 and ext4
+    case MSDOS_SUPER_MAGIC:
+    case REISERFS_SUPER_MAGIC:
+    case BTRFS_SUPER_MAGIC:
+    case 0x5346544E:  // NTFS
+    case 0x58465342:  // XFS
+    case 0x3153464A:  // JFS
+      *type = FILE_SYSTEM_ORDINARY;
+      break;
+    case NFS_SUPER_MAGIC:
+      *type = FILE_SYSTEM_NFS;
+      break;
+    case SMB_SUPER_MAGIC:
+    case 0xFF534D42:  // CIFS
+      *type = FILE_SYSTEM_SMB;
+      break;
+    case CODA_SUPER_MAGIC:
+      *type = FILE_SYSTEM_CODA;
+      break;
+    case HUGETLBFS_MAGIC:
+    case RAMFS_MAGIC:
+    case TMPFS_MAGIC:
+      *type = FILE_SYSTEM_MEMORY;
+      break;
+    case CGROUP_SUPER_MAGIC:
+      *type = FILE_SYSTEM_CGROUP;
+      break;
+    default:
+      *type = FILE_SYSTEM_OTHER;
+  }
+  return true;
+}
+
+}  // namespace base
diff --git a/src/base/files/file_util_posix.cc b/src/base/files/file_util_posix.cc
new file mode 100644 (file)
index 0000000..7ee1645
--- /dev/null
@@ -0,0 +1,664 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <iterator>
+
+#include "base/command_line.h"
+#include "base/containers/stack.h"
+#include "base/environment.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <AvailabilityMacros.h>
+#endif
+
+#if !defined(OS_IOS)
+#include <grp.h>
+#endif
+
+// We need to do this on AIX due to some inconsistencies in how AIX
+// handles XOPEN_SOURCE and ALL_SOURCE.
+#if defined(OS_AIX)
+extern "C" char* mkdtemp(char* path);
+#endif
+
+namespace base {
+
+namespace {
+
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+int CallStat(const char* path, stat_wrapper_t* sb) {
+  return stat(path, sb);
+}
+int CallLstat(const char* path, stat_wrapper_t* sb) {
+  return lstat(path, sb);
+}
+#else
+int CallStat(const char* path, stat_wrapper_t* sb) {
+  return stat64(path, sb);
+}
+int CallLstat(const char* path, stat_wrapper_t* sb) {
+  return lstat64(path, sb);
+}
+#endif
+
+// Helper for VerifyPathControlledByUser.
+bool VerifySpecificPathControlledByUser(const FilePath& path,
+                                        uid_t owner_uid,
+                                        const std::set<gid_t>& group_gids) {
+  stat_wrapper_t stat_info;
+  if (CallLstat(path.value().c_str(), &stat_info) != 0) {
+    DPLOG(ERROR) << "Failed to get information on path " << path.value();
+    return false;
+  }
+
+  if (S_ISLNK(stat_info.st_mode)) {
+    DLOG(ERROR) << "Path " << path.value() << " is a symbolic link.";
+    return false;
+  }
+
+  if (stat_info.st_uid != owner_uid) {
+    DLOG(ERROR) << "Path " << path.value() << " is owned by the wrong user.";
+    return false;
+  }
+
+  if ((stat_info.st_mode & S_IWGRP) &&
+      !ContainsKey(group_gids, stat_info.st_gid)) {
+    DLOG(ERROR) << "Path " << path.value()
+                << " is writable by an unprivileged group.";
+    return false;
+  }
+
+  if (stat_info.st_mode & S_IWOTH) {
+    DLOG(ERROR) << "Path " << path.value() << " is writable by any user.";
+    return false;
+  }
+
+  return true;
+}
+
+std::string TempFileName() {
+  return std::string(".org.chromium.Chromium.XXXXXX");
+}
+
+#if !defined(OS_MACOSX)
+bool CopyFileContents(File* infile, File* outfile) {
+  static constexpr size_t kBufferSize = 32768;
+  std::vector<char> buffer(kBufferSize);
+
+  for (;;) {
+    ssize_t bytes_read = infile->ReadAtCurrentPos(buffer.data(), buffer.size());
+    if (bytes_read < 0)
+      return false;
+    if (bytes_read == 0)
+      return true;
+    // Allow for partial writes
+    ssize_t bytes_written_per_read = 0;
+    do {
+      ssize_t bytes_written_partial = outfile->WriteAtCurrentPos(
+          &buffer[bytes_written_per_read], bytes_read - bytes_written_per_read);
+      if (bytes_written_partial < 0)
+        return false;
+
+      bytes_written_per_read += bytes_written_partial;
+    } while (bytes_written_per_read < bytes_read);
+  }
+
+  NOTREACHED();
+  return false;
+}
+
+// Appends |mode_char| to |mode| before the optional character set encoding; see
+// https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html for
+// details.
+std::string AppendModeCharacter(std::string_view mode, char mode_char) {
+  std::string result(mode);
+  size_t comma_pos = result.find(',');
+  result.insert(comma_pos == std::string::npos ? result.length() : comma_pos, 1,
+                mode_char);
+  return result;
+}
+#endif
+
+}  // namespace
+
+FilePath MakeAbsoluteFilePath(const FilePath& input) {
+  char full_path[PATH_MAX];
+  if (realpath(input.value().c_str(), full_path) == nullptr)
+    return FilePath();
+  return FilePath(full_path);
+}
+
+// TODO(erikkay): The Windows version of this accepts paths like "foo/bar/*"
+// which works both with and without the recursive flag.  I'm not sure we need
+// that functionality. If not, remove from file_util_win.cc, otherwise add it
+// here.
+bool DeleteFile(const FilePath& path, bool recursive) {
+  const char* path_str = path.value().c_str();
+  stat_wrapper_t file_info;
+  if (CallLstat(path_str, &file_info) != 0) {
+    // The Windows version defines this condition as success.
+    return (errno == ENOENT || errno == ENOTDIR);
+  }
+  if (!S_ISDIR(file_info.st_mode))
+    return (unlink(path_str) == 0);
+  if (!recursive)
+    return (rmdir(path_str) == 0);
+
+  bool success = true;
+  stack<std::string> directories;
+  directories.push(path.value());
+  FileEnumerator traversal(path, true,
+                           FileEnumerator::FILES | FileEnumerator::DIRECTORIES |
+                               FileEnumerator::SHOW_SYM_LINKS);
+  for (FilePath current = traversal.Next(); !current.empty();
+       current = traversal.Next()) {
+    if (traversal.GetInfo().IsDirectory())
+      directories.push(current.value());
+    else
+      success &= (unlink(current.value().c_str()) == 0);
+  }
+
+  while (!directories.empty()) {
+    FilePath dir = FilePath(directories.top());
+    directories.pop();
+    success &= (rmdir(dir.value().c_str()) == 0);
+  }
+  return success;
+}
+
+bool ReplaceFile(const FilePath& from_path,
+                 const FilePath& to_path,
+                 File::Error* error) {
+  if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0)
+    return true;
+  if (error)
+    *error = File::GetLastFileError();
+  return false;
+}
+
+bool CreateLocalNonBlockingPipe(int fds[2]) {
+#if defined(OS_LINUX) || defined(OS_BSD)
+  return pipe2(fds, O_CLOEXEC | O_NONBLOCK) == 0;
+#else
+  int raw_fds[2];
+  if (pipe(raw_fds) != 0)
+    return false;
+  ScopedFD fd_out(raw_fds[0]);
+  ScopedFD fd_in(raw_fds[1]);
+  if (!SetCloseOnExec(fd_out.get()))
+    return false;
+  if (!SetCloseOnExec(fd_in.get()))
+    return false;
+  if (!SetNonBlocking(fd_out.get()))
+    return false;
+  if (!SetNonBlocking(fd_in.get()))
+    return false;
+  fds[0] = fd_out.release();
+  fds[1] = fd_in.release();
+  return true;
+#endif
+}
+
+bool SetNonBlocking(int fd) {
+  const int flags = fcntl(fd, F_GETFL);
+  if (flags == -1)
+    return false;
+  if (flags & O_NONBLOCK)
+    return true;
+  if (HANDLE_EINTR(fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1)
+    return false;
+  return true;
+}
+
+bool SetCloseOnExec(int fd) {
+  const int flags = fcntl(fd, F_GETFD);
+  if (flags == -1)
+    return false;
+  if (flags & FD_CLOEXEC)
+    return true;
+  if (HANDLE_EINTR(fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1)
+    return false;
+  return true;
+}
+
+bool PathExists(const FilePath& path) {
+  return access(path.value().c_str(), F_OK) == 0;
+}
+
+bool PathIsWritable(const FilePath& path) {
+  return access(path.value().c_str(), W_OK) == 0;
+}
+
+bool DirectoryExists(const FilePath& path) {
+  stat_wrapper_t file_info;
+  if (CallStat(path.value().c_str(), &file_info) != 0)
+    return false;
+  return S_ISDIR(file_info.st_mode);
+}
+
+#if !defined(OS_FUCHSIA)
+bool CreateSymbolicLink(const FilePath& target_path,
+                        const FilePath& symlink_path) {
+  DCHECK(!symlink_path.empty());
+  DCHECK(!target_path.empty());
+  return ::symlink(target_path.value().c_str(), symlink_path.value().c_str()) !=
+         -1;
+}
+
+bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) {
+  DCHECK(!symlink_path.empty());
+  DCHECK(target_path);
+  char buf[PATH_MAX];
+  ssize_t count = ::readlink(symlink_path.value().c_str(), buf, std::size(buf));
+
+  if (count <= 0) {
+    target_path->clear();
+    return false;
+  }
+
+  *target_path = FilePath(FilePath::StringType(buf, count));
+  return true;
+}
+
+bool GetPosixFilePermissions(const FilePath& path, int* mode) {
+  DCHECK(mode);
+
+  stat_wrapper_t file_info;
+  // Uses stat(), because on symbolic link, lstat() does not return valid
+  // permission bits in st_mode
+  if (CallStat(path.value().c_str(), &file_info) != 0)
+    return false;
+
+  *mode = file_info.st_mode & FILE_PERMISSION_MASK;
+  return true;
+}
+
+bool SetPosixFilePermissions(const FilePath& path, int mode) {
+  DCHECK_EQ(mode & ~FILE_PERMISSION_MASK, 0);
+
+  // Calls stat() so that we can preserve the higher bits like S_ISGID.
+  stat_wrapper_t stat_buf;
+  if (CallStat(path.value().c_str(), &stat_buf) != 0)
+    return false;
+
+  // Clears the existing permission bits, and adds the new ones.
+  mode_t updated_mode_bits = stat_buf.st_mode & ~FILE_PERMISSION_MASK;
+  updated_mode_bits |= mode & FILE_PERMISSION_MASK;
+
+  if (HANDLE_EINTR(chmod(path.value().c_str(), updated_mode_bits)) != 0)
+    return false;
+
+  return true;
+}
+
+bool ExecutableExistsInPath(Environment* env,
+                            const FilePath::StringType& executable) {
+  std::string path;
+  if (!env->GetVar("PATH", &path)) {
+    LOG(ERROR) << "No $PATH variable. Assuming no " << executable << ".";
+    return false;
+  }
+
+  for (const std::string_view& cur_path :
+       SplitStringPiece(path, ":", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
+    FilePath file(cur_path);
+    int permissions;
+    if (GetPosixFilePermissions(file.Append(executable), &permissions) &&
+        (permissions & FILE_PERMISSION_EXECUTE_BY_USER))
+      return true;
+  }
+  return false;
+}
+
+#endif  // !OS_FUCHSIA
+
+bool GetTempDir(FilePath* path) {
+  const char* tmp = getenv("TMPDIR");
+  if (tmp) {
+    *path = FilePath(tmp);
+    return true;
+  }
+
+  *path = FilePath("/tmp");
+  return true;
+}
+
+#if !defined(OS_MACOSX)  // Mac implementation is in file_util_mac.mm.
+FilePath GetHomeDir() {
+  const char* home_dir = getenv("HOME");
+  if (home_dir && home_dir[0])
+    return FilePath(home_dir);
+
+  FilePath rv;
+  if (GetTempDir(&rv))
+    return rv;
+
+  // Last resort.
+  return FilePath("/tmp");
+}
+#endif  // !defined(OS_MACOSX)
+
+static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir,
+                                        const FilePath::StringType& name_tmpl,
+                                        FilePath* new_dir) {
+  DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos)
+      << "Directory name template must contain \"XXXXXX\".";
+
+  FilePath sub_dir = base_dir.Append(name_tmpl);
+  std::string sub_dir_string = sub_dir.value();
+
+  // this should be OK since mkdtemp just replaces characters in place
+  char* buffer = const_cast<char*>(sub_dir_string.c_str());
+  char* dtemp = mkdtemp(buffer);
+  if (!dtemp) {
+    DPLOG(ERROR) << "mkdtemp";
+    return false;
+  }
+  *new_dir = FilePath(dtemp);
+  return true;
+}
+
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir) {
+  FilePath::StringType mkdtemp_template = prefix;
+  mkdtemp_template.append(FILE_PATH_LITERAL("XXXXXX"));
+  return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir);
+}
+
+bool CreateNewTempDirectory(const FilePath::StringType& prefix,
+                            FilePath* new_temp_path) {
+  FilePath tmpdir;
+  if (!GetTempDir(&tmpdir))
+    return false;
+
+  return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path);
+}
+
+bool CreateDirectoryAndGetError(const FilePath& full_path, File::Error* error) {
+  std::vector<FilePath> subpaths;
+
+  // Collect a list of all parent directories.
+  FilePath last_path = full_path;
+  subpaths.push_back(full_path);
+  for (FilePath path = full_path.DirName(); path.value() != last_path.value();
+       path = path.DirName()) {
+    subpaths.push_back(path);
+    last_path = path;
+  }
+
+  // Iterate through the parents and create the missing ones.
+  for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin();
+       i != subpaths.rend(); ++i) {
+    if (DirectoryExists(*i))
+      continue;
+    if (mkdir(i->value().c_str(), 0700) == 0)
+      continue;
+    // Mkdir failed, but it might have failed with EEXIST, or some other error
+    // due to the the directory appearing out of thin air. This can occur if
+    // two processes are trying to create the same file system tree at the same
+    // time. Check to see if it exists and make sure it is a directory.
+    int saved_errno = errno;
+    if (!DirectoryExists(*i)) {
+      if (error)
+        *error = File::OSErrorToFileError(saved_errno);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) {
+  FilePath real_path_result = MakeAbsoluteFilePath(path);
+  if (real_path_result.empty())
+    return false;
+
+  // To be consistent with windows, fail if |real_path_result| is a
+  // directory.
+  if (DirectoryExists(real_path_result))
+    return false;
+
+  *normalized_path = real_path_result;
+  return true;
+}
+
+// TODO(rkc): Refactor GetFileInfo and FileEnumerator to handle symlinks
+// correctly. http://code.google.com/p/chromium-os/issues/detail?id=15948
+bool IsLink(const FilePath& file_path) {
+  stat_wrapper_t st;
+  // If we can't lstat the file, it's safe to assume that the file won't at
+  // least be a 'followable' link.
+  if (CallLstat(file_path.value().c_str(), &st) != 0)
+    return false;
+  return S_ISLNK(st.st_mode);
+}
+
+bool GetFileInfo(const FilePath& file_path, File::Info* results) {
+  stat_wrapper_t file_info;
+  if (CallStat(file_path.value().c_str(), &file_info) != 0)
+    return false;
+
+  results->FromStat(file_info);
+  return true;
+}
+
+FILE* OpenFile(const FilePath& filename, const char* mode) {
+  // 'e' is unconditionally added below, so be sure there is not one already
+  // present before a comma in |mode|.
+  DCHECK(
+      strchr(mode, 'e') == nullptr ||
+      (strchr(mode, ',') != nullptr && strchr(mode, 'e') > strchr(mode, ',')));
+  FILE* result = nullptr;
+#if defined(OS_MACOSX)
+  // macOS does not provide a mode character to set O_CLOEXEC; see
+  // https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/fopen.3.html.
+  const char* the_mode = mode;
+#else
+  std::string mode_with_e(AppendModeCharacter(mode, 'e'));
+  const char* the_mode = mode_with_e.c_str();
+#endif
+  do {
+    result = fopen(filename.value().c_str(), the_mode);
+  } while (!result && errno == EINTR);
+#if defined(OS_MACOSX)
+  // Mark the descriptor as close-on-exec.
+  if (result)
+    SetCloseOnExec(fileno(result));
+#endif
+  return result;
+}
+
+FILE* FileToFILE(File file, const char* mode) {
+  FILE* stream = fdopen(file.GetPlatformFile(), mode);
+  if (stream)
+    file.TakePlatformFile();
+  return stream;
+}
+
+int ReadFile(const FilePath& filename, char* data, int max_size) {
+  int fd = HANDLE_EINTR(open(filename.value().c_str(), O_RDONLY));
+  if (fd < 0)
+    return -1;
+
+  ssize_t bytes_read = HANDLE_EINTR(read(fd, data, max_size));
+  if (IGNORE_EINTR(close(fd)) < 0)
+    return -1;
+  return bytes_read;
+}
+
+int WriteFile(const FilePath& filename, const char* data, int size) {
+  int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0666));
+  if (fd < 0)
+    return -1;
+
+  int bytes_written = WriteFileDescriptor(fd, data, size) ? size : -1;
+  if (IGNORE_EINTR(close(fd)) < 0)
+    return -1;
+  return bytes_written;
+}
+
+bool WriteFileDescriptor(const int fd, const char* data, int size) {
+  // Allow for partial writes.
+  ssize_t bytes_written_total = 0;
+  for (ssize_t bytes_written_partial = 0; bytes_written_total < size;
+       bytes_written_total += bytes_written_partial) {
+    bytes_written_partial = HANDLE_EINTR(
+        write(fd, data + bytes_written_total, size - bytes_written_total));
+    if (bytes_written_partial < 0)
+      return false;
+  }
+
+  return true;
+}
+
+bool AppendToFile(const FilePath& filename, const char* data, int size) {
+  bool ret = true;
+  int fd = HANDLE_EINTR(open(filename.value().c_str(), O_WRONLY | O_APPEND));
+  if (fd < 0) {
+    return false;
+  }
+
+  // This call will either write all of the data or return false.
+  if (!WriteFileDescriptor(fd, data, size)) {
+    ret = false;
+  }
+
+  if (IGNORE_EINTR(close(fd)) < 0) {
+    return false;
+  }
+
+  return ret;
+}
+
+bool GetCurrentDirectory(FilePath* dir) {
+  char system_buffer[PATH_MAX] = "";
+  if (!getcwd(system_buffer, sizeof(system_buffer))) {
+    NOTREACHED();
+    return false;
+  }
+  *dir = FilePath(system_buffer);
+  return true;
+}
+
+bool SetCurrentDirectory(const FilePath& path) {
+  return chdir(path.value().c_str()) == 0;
+}
+
+bool VerifyPathControlledByUser(const FilePath& base,
+                                const FilePath& path,
+                                uid_t owner_uid,
+                                const std::set<gid_t>& group_gids) {
+  if (base != path && !base.IsParent(path)) {
+    DLOG(ERROR) << "|base| must be a subdirectory of |path|.  base = \""
+                << base.value() << "\", path = \"" << path.value() << "\"";
+    return false;
+  }
+
+  std::vector<FilePath::StringType> base_components;
+  std::vector<FilePath::StringType> path_components;
+
+  base.GetComponents(&base_components);
+  path.GetComponents(&path_components);
+
+  std::vector<FilePath::StringType>::const_iterator ib, ip;
+  for (ib = base_components.begin(), ip = path_components.begin();
+       ib != base_components.end(); ++ib, ++ip) {
+    // |base| must be a subpath of |path|, so all components should match.
+    // If these CHECKs fail, look at the test that base is a parent of
+    // path at the top of this function.
+    DCHECK(ip != path_components.end());
+    DCHECK(*ip == *ib);
+  }
+
+  FilePath current_path = base;
+  if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gids))
+    return false;
+
+  for (; ip != path_components.end(); ++ip) {
+    current_path = current_path.Append(*ip);
+    if (!VerifySpecificPathControlledByUser(current_path, owner_uid,
+                                            group_gids))
+      return false;
+  }
+  return true;
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+bool VerifyPathControlledByAdmin(const FilePath& path) {
+  const unsigned kRootUid = 0;
+  const FilePath kFileSystemRoot("/");
+
+  // The name of the administrator group on mac os.
+  const char* const kAdminGroupNames[] = {"admin", "wheel"};
+
+  std::set<gid_t> allowed_group_ids;
+  for (int i = 0, ie = std::size(kAdminGroupNames); i < ie; ++i) {
+    struct group* group_record = getgrnam(kAdminGroupNames[i]);
+    if (!group_record) {
+      DPLOG(ERROR) << "Could not get the group ID of group \""
+                   << kAdminGroupNames[i] << "\".";
+      continue;
+    }
+
+    allowed_group_ids.insert(group_record->gr_gid);
+  }
+
+  return VerifyPathControlledByUser(kFileSystemRoot, path, kRootUid,
+                                    allowed_group_ids);
+}
+#endif  // defined(OS_MACOSX) && !defined(OS_IOS)
+
+int GetMaximumPathComponentLength(const FilePath& path) {
+  return pathconf(path.value().c_str(), _PC_NAME_MAX);
+}
+
+#if !defined(OS_MACOSX)
+// Mac has its own implementation, this is for all other Posix systems.
+bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
+  File infile;
+  infile = File(from_path, File::FLAG_OPEN | File::FLAG_READ);
+  if (!infile.IsValid())
+    return false;
+
+  File outfile(to_path, File::FLAG_WRITE | File::FLAG_CREATE_ALWAYS);
+  if (!outfile.IsValid())
+    return false;
+
+  return CopyFileContents(&infile, &outfile);
+}
+#endif  // !defined(OS_MACOSX)
+
+}  // namespace base
diff --git a/src/base/files/file_util_win.cc b/src/base/files/file_util_win.cc
new file mode 100644 (file)
index 0000000..a18715e
--- /dev/null
@@ -0,0 +1,635 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+
+// windows.h includes winsock.h which isn't compatible with winsock2.h. To use winsock2.h
+// you have to include it first.
+#include <winsock2.h>
+#include <windows.h>
+
+#include <io.h>
+#include <psapi.h>
+#include <share.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <string>
+#include <string_view>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
+
+// #define needed to link in RtlGenRandom(), a.k.a. SystemFunction036.  See the
+// "Community Additions" comment on MSDN here:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx
+#define SystemFunction036 NTAPI SystemFunction036
+#include <ntsecapi.h>
+#undef SystemFunction036
+
+namespace base {
+
+namespace {
+
+const DWORD kFileShareAll =
+    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+
+// Deletes all files and directories in a path.
+// Returns ERROR_SUCCESS on success or the Windows error code corresponding to
+// the first error encountered.
+DWORD DeleteFileRecursive(const FilePath& path,
+                          const FilePath::StringType& pattern,
+                          bool recursive) {
+  FileEnumerator traversal(path, false,
+                           FileEnumerator::FILES | FileEnumerator::DIRECTORIES,
+                           pattern);
+  DWORD result = ERROR_SUCCESS;
+  for (FilePath current = traversal.Next(); !current.empty();
+       current = traversal.Next()) {
+    // Try to clear the read-only bit if we find it.
+    FileEnumerator::FileInfo info = traversal.GetInfo();
+    if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) &&
+        (recursive || !info.IsDirectory())) {
+      ::SetFileAttributes(
+          ToWCharT(&current.value()),
+          info.find_data().dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
+    }
+
+    DWORD this_result = ERROR_SUCCESS;
+    if (info.IsDirectory()) {
+      if (recursive) {
+        this_result = DeleteFileRecursive(current, pattern, true);
+        if (this_result == ERROR_SUCCESS &&
+            !::RemoveDirectory(ToWCharT(&current.value()))) {
+          this_result = ::GetLastError();
+        }
+      }
+    } else if (!::DeleteFile(ToWCharT(&current.value()))) {
+      this_result = ::GetLastError();
+    }
+    if (result == ERROR_SUCCESS)
+      result = this_result;
+  }
+  return result;
+}
+
+// Appends |mode_char| to |mode| before the optional character set encoding; see
+// https://msdn.microsoft.com/library/yeby3zcb.aspx for details.
+void AppendModeCharacter(char16_t mode_char, std::u16string* mode) {
+  size_t comma_pos = mode->find(L',');
+  mode->insert(comma_pos == std::u16string::npos ? mode->length() : comma_pos,
+               1, mode_char);
+}
+
+// Returns ERROR_SUCCESS on success, or a Windows error code on failure.
+DWORD DoDeleteFile(const FilePath& path, bool recursive) {
+  if (path.empty())
+    return ERROR_SUCCESS;
+
+  if (path.value().length() >= MAX_PATH)
+    return ERROR_BAD_PATHNAME;
+
+  // Handle any path with wildcards.
+  if (path.BaseName().value().find_first_of(u"*?") !=
+      FilePath::StringType::npos) {
+    return DeleteFileRecursive(path.DirName(), path.BaseName().value(),
+                               recursive);
+  }
+
+  // Report success if the file or path does not exist.
+  const DWORD attr = ::GetFileAttributes(ToWCharT(&path.value()));
+  if (attr == INVALID_FILE_ATTRIBUTES) {
+    const DWORD error_code = ::GetLastError();
+    return (error_code == ERROR_FILE_NOT_FOUND ||
+            error_code == ERROR_PATH_NOT_FOUND)
+               ? ERROR_SUCCESS
+               : error_code;
+  }
+
+  // Clear the read-only bit if it is set.
+  if ((attr & FILE_ATTRIBUTE_READONLY) &&
+      !::SetFileAttributes(ToWCharT(&path.value()),
+                           attr & ~FILE_ATTRIBUTE_READONLY)) {
+    return ::GetLastError();
+  }
+
+  // Perform a simple delete on anything that isn't a directory.
+  if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
+    return ::DeleteFile(ToWCharT(&path.value())) ? ERROR_SUCCESS
+                                                 : ::GetLastError();
+  }
+
+  if (recursive) {
+    const DWORD error_code = DeleteFileRecursive(path, u"*", true);
+    if (error_code != ERROR_SUCCESS)
+      return error_code;
+  }
+  return ::RemoveDirectory(ToWCharT(&path.value())) ? ERROR_SUCCESS
+                                                    : ::GetLastError();
+}
+
+std::string RandomDataToGUIDString(const uint64_t bytes[2]) {
+  return base::StringPrintf(
+      "%08x-%04x-%04x-%04x-%012llx", static_cast<unsigned int>(bytes[0] >> 32),
+      static_cast<unsigned int>((bytes[0] >> 16) & 0x0000ffff),
+      static_cast<unsigned int>(bytes[0] & 0x0000ffff),
+      static_cast<unsigned int>(bytes[1] >> 48),
+      bytes[1] & 0x0000ffff'ffffffffULL);
+}
+
+void RandBytes(void* output, size_t output_length) {
+  char* output_ptr = static_cast<char*>(output);
+  while (output_length > 0) {
+    const ULONG output_bytes_this_pass = static_cast<ULONG>(std::min(
+        output_length, static_cast<size_t>(std::numeric_limits<ULONG>::max())));
+    const bool success =
+        RtlGenRandom(output_ptr, output_bytes_this_pass) != FALSE;
+    CHECK(success);
+    output_length -= output_bytes_this_pass;
+    output_ptr += output_bytes_this_pass;
+  }
+}
+
+std::string GenerateGUID() {
+  uint64_t sixteen_bytes[2];
+  // Use base::RandBytes instead of crypto::RandBytes, because crypto calls the
+  // base version directly, and to prevent the dependency from base/ to crypto/.
+  RandBytes(&sixteen_bytes, sizeof(sixteen_bytes));
+
+  // Set the GUID to version 4 as described in RFC 4122, section 4.4.
+  // The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
+  // where y is one of [8, 9, A, B].
+
+  // Clear the version bits and set the version to 4:
+  sixteen_bytes[0] &= 0xffffffff'ffff0fffULL;
+  sixteen_bytes[0] |= 0x00000000'00004000ULL;
+
+  // Set the two most significant bits (bits 6 and 7) of the
+  // clock_seq_hi_and_reserved to zero and one, respectively:
+  sixteen_bytes[1] &= 0x3fffffff'ffffffffULL;
+  sixteen_bytes[1] |= 0x80000000'00000000ULL;
+
+  return RandomDataToGUIDString(sixteen_bytes);
+}
+
+}  // namespace
+
+FilePath MakeAbsoluteFilePath(const FilePath& input) {
+  char16_t file_path[MAX_PATH];
+  if (!_wfullpath(ToWCharT(file_path), ToWCharT(&input.value()), MAX_PATH))
+    return FilePath();
+  return FilePath(file_path);
+}
+
+bool DeleteFile(const FilePath& path, bool recursive) {
+  static constexpr char kRecursive[] = "DeleteFile.Recursive";
+  static constexpr char kNonRecursive[] = "DeleteFile.NonRecursive";
+  const std::string_view operation(recursive ? kRecursive : kNonRecursive);
+
+  // Metrics for delete failures tracked in https://crbug.com/599084. Delete may
+  // fail for a number of reasons. Log some metrics relating to failures in the
+  // current code so that any improvements or regressions resulting from
+  // subsequent code changes can be detected.
+  const DWORD error = DoDeleteFile(path, recursive);
+  return error == ERROR_SUCCESS;
+}
+
+bool DeleteFileAfterReboot(const FilePath& path) {
+  if (path.value().length() >= MAX_PATH)
+    return false;
+
+  return MoveFileEx(ToWCharT(&path.value()), NULL,
+                    MOVEFILE_DELAY_UNTIL_REBOOT | MOVEFILE_REPLACE_EXISTING) !=
+         FALSE;
+}
+
+bool ReplaceFile(const FilePath& from_path,
+                 const FilePath& to_path,
+                 File::Error* error) {
+  // Try a simple move first.  It will only succeed when |to_path| doesn't
+  // already exist.
+  if (::MoveFile(ToWCharT(&from_path.value()), ToWCharT(&to_path.value())))
+    return true;
+  File::Error move_error = File::OSErrorToFileError(GetLastError());
+
+  // Try the full-blown replace if the move fails, as ReplaceFile will only
+  // succeed when |to_path| does exist. When writing to a network share, we may
+  // not be able to change the ACLs. Ignore ACL errors then
+  // (REPLACEFILE_IGNORE_MERGE_ERRORS).
+  if (::ReplaceFile(ToWCharT(&to_path.value()), ToWCharT(&from_path.value()),
+                    NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) {
+    return true;
+  }
+  // In the case of FILE_ERROR_NOT_FOUND from ReplaceFile, it is likely that
+  // |to_path| does not exist. In this case, the more relevant error comes
+  // from the call to MoveFile.
+  if (error) {
+    File::Error replace_error = File::OSErrorToFileError(GetLastError());
+    *error = replace_error == File::FILE_ERROR_NOT_FOUND ? move_error
+                                                         : replace_error;
+  }
+  return false;
+}
+
+bool PathExists(const FilePath& path) {
+  return (GetFileAttributes(ToWCharT(&path.value())) !=
+          INVALID_FILE_ATTRIBUTES);
+}
+
+bool PathIsWritable(const FilePath& path) {
+  HANDLE dir =
+      CreateFile(ToWCharT(&path.value()), FILE_ADD_FILE, kFileShareAll, NULL,
+                 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+  if (dir == INVALID_HANDLE_VALUE)
+    return false;
+
+  CloseHandle(dir);
+  return true;
+}
+
+bool DirectoryExists(const FilePath& path) {
+  DWORD fileattr = GetFileAttributes(ToWCharT(&path.value()));
+  if (fileattr != INVALID_FILE_ATTRIBUTES)
+    return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0;
+  return false;
+}
+
+bool GetTempDir(FilePath* path) {
+  char16_t temp_path[MAX_PATH + 1];
+  DWORD path_len = ::GetTempPath(MAX_PATH, ToWCharT(temp_path));
+  if (path_len >= MAX_PATH || path_len <= 0)
+    return false;
+  // TODO(evanm): the old behavior of this function was to always strip the
+  // trailing slash.  We duplicate this here, but it shouldn't be necessary
+  // when everyone is using the appropriate FilePath APIs.
+  *path = FilePath(temp_path).StripTrailingSeparators();
+  return true;
+}
+
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir) {
+  FilePath path_to_create;
+
+  for (int count = 0; count < 50; ++count) {
+    // Try create a new temporary directory with random generated name. If
+    // the one exists, keep trying another path name until we reach some limit.
+    std::u16string new_dir_name;
+    new_dir_name.assign(prefix);
+    new_dir_name.append(IntToString16(::GetCurrentProcessId()));
+    new_dir_name.push_back('_');
+    new_dir_name.append(UTF8ToUTF16(GenerateGUID()));
+
+    path_to_create = base_dir.Append(new_dir_name);
+    if (::CreateDirectory(ToWCharT(&path_to_create.value()), NULL)) {
+      *new_dir = path_to_create;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool CreateNewTempDirectory(const FilePath::StringType& prefix,
+                            FilePath* new_temp_path) {
+  FilePath system_temp_dir;
+  if (!GetTempDir(&system_temp_dir))
+    return false;
+
+  return CreateTemporaryDirInDir(system_temp_dir, prefix, new_temp_path);
+}
+
+bool CreateDirectoryAndGetError(const FilePath& full_path, File::Error* error) {
+  // If the path exists, we've succeeded if it's a directory, failed otherwise.
+  DWORD fileattr = ::GetFileAttributes(ToWCharT(&full_path.value()));
+  if (fileattr != INVALID_FILE_ATTRIBUTES) {
+    if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+      return true;
+    }
+    DLOG(WARNING) << "CreateDirectory(" << UTF16ToUTF8(full_path.value())
+                  << "), "
+                  << "conflicts with existing file.";
+    if (error) {
+      *error = File::FILE_ERROR_NOT_A_DIRECTORY;
+    }
+    return false;
+  }
+
+  // Invariant:  Path does not exist as file or directory.
+
+  // Attempt to create the parent recursively.  This will immediately return
+  // true if it already exists, otherwise will create all required parent
+  // directories starting with the highest-level missing parent.
+  FilePath parent_path(full_path.DirName());
+  if (parent_path.value() == full_path.value()) {
+    if (error) {
+      *error = File::FILE_ERROR_NOT_FOUND;
+    }
+    return false;
+  }
+  if (!CreateDirectoryAndGetError(parent_path, error)) {
+    DLOG(WARNING) << "Failed to create one of the parent directories.";
+    if (error) {
+      DCHECK(*error != File::FILE_OK);
+    }
+    return false;
+  }
+
+  if (!::CreateDirectory(ToWCharT(&full_path.value()), NULL)) {
+    DWORD error_code = ::GetLastError();
+    if (error_code == ERROR_ALREADY_EXISTS && DirectoryExists(full_path)) {
+      // This error code ERROR_ALREADY_EXISTS doesn't indicate whether we
+      // were racing with someone creating the same directory, or a file
+      // with the same path.  If DirectoryExists() returns true, we lost the
+      // race to create the same directory.
+      return true;
+    } else {
+      if (error)
+        *error = File::OSErrorToFileError(error_code);
+      DLOG(WARNING) << "Failed to create directory "
+                    << UTF16ToUTF8(full_path.value()) << ", last error is "
+                    << error_code << ".";
+      return false;
+    }
+  } else {
+    return true;
+  }
+}
+
+bool NormalizeFilePath(const FilePath& path, FilePath* real_path) {
+  FilePath mapped_file;
+  if (!NormalizeToNativeFilePath(path, &mapped_file))
+    return false;
+  // NormalizeToNativeFilePath() will return a path that starts with
+  // "\Device\Harddisk...".  Helper DevicePathToDriveLetterPath()
+  // will find a drive letter which maps to the path's device, so
+  // that we return a path starting with a drive letter.
+  return DevicePathToDriveLetterPath(mapped_file, real_path);
+}
+
+bool DevicePathToDriveLetterPath(const FilePath& nt_device_path,
+                                 FilePath* out_drive_letter_path) {
+  // Get the mapping of drive letters to device paths.
+  const int kDriveMappingSize = 1024;
+  char16_t drive_mapping[kDriveMappingSize] = {'\0'};
+  if (!::GetLogicalDriveStrings(kDriveMappingSize - 1,
+                                ToWCharT(drive_mapping))) {
+    DLOG(ERROR) << "Failed to get drive mapping.";
+    return false;
+  }
+
+  // The drive mapping is a sequence of null terminated strings.
+  // The last string is empty.
+  char16_t* drive_map_ptr = drive_mapping;
+  char16_t device_path_as_string[MAX_PATH];
+  char16_t drive[] = u" :";
+
+  // For each string in the drive mapping, get the junction that links
+  // to it.  If that junction is a prefix of |device_path|, then we
+  // know that |drive| is the real path prefix.
+  while (*drive_map_ptr) {
+    drive[0] = drive_map_ptr[0];  // Copy the drive letter.
+
+    if (QueryDosDevice(ToWCharT(drive), ToWCharT(device_path_as_string),
+                       MAX_PATH)) {
+      FilePath device_path(device_path_as_string);
+      if (device_path == nt_device_path ||
+          device_path.IsParent(nt_device_path)) {
+        *out_drive_letter_path =
+            FilePath(drive + nt_device_path.value().substr(
+                                 wcslen(ToWCharT(device_path_as_string))));
+        return true;
+      }
+    }
+    // Move to the next drive letter string, which starts one
+    // increment after the '\0' that terminates the current string.
+    while (*drive_map_ptr++) {
+    }
+  }
+
+  // No drive matched.  The path does not start with a device junction
+  // that is mounted as a drive letter.  This means there is no drive
+  // letter path to the volume that holds |device_path|, so fail.
+  return false;
+}
+
+bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path) {
+  // In Vista, GetFinalPathNameByHandle() would give us the real path
+  // from a file handle.  If we ever deprecate XP, consider changing the
+  // code below to a call to GetFinalPathNameByHandle().  The method this
+  // function uses is explained in the following msdn article:
+  // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
+  win::ScopedHandle file_handle(
+      ::CreateFile(ToWCharT(&path.value()), GENERIC_READ, kFileShareAll, NULL,
+                   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
+  if (!file_handle.IsValid())
+    return false;
+
+  // Create a file mapping object.  Can't easily use MemoryMappedFile, because
+  // we only map the first byte, and need direct access to the handle. You can
+  // not map an empty file, this call fails in that case.
+  win::ScopedHandle file_map_handle(
+      ::CreateFileMapping(file_handle.Get(), NULL, PAGE_READONLY, 0,
+                          1,  // Just one byte.  No need to look at the data.
+                          NULL));
+  if (!file_map_handle.IsValid())
+    return false;
+
+  // Use a view of the file to get the path to the file.
+  void* file_view =
+      MapViewOfFile(file_map_handle.Get(), FILE_MAP_READ, 0, 0, 1);
+  if (!file_view)
+    return false;
+
+  // The expansion of |path| into a full path may make it longer.
+  // GetMappedFileName() will fail if the result is longer than MAX_PATH.
+  // Pad a bit to be safe.  If kMaxPathLength is ever changed to be less
+  // than MAX_PATH, it would be nessisary to test that GetMappedFileName()
+  // not return kMaxPathLength.  This would mean that only part of the
+  // path fit in |mapped_file_path|.
+  const int kMaxPathLength = MAX_PATH + 10;
+  char16_t mapped_file_path[kMaxPathLength];
+  bool success = false;
+  HANDLE cp = GetCurrentProcess();
+  if (::GetMappedFileNameW(cp, file_view, ToWCharT(mapped_file_path),
+                           kMaxPathLength)) {
+    *nt_path = FilePath(mapped_file_path);
+    success = true;
+  }
+  ::UnmapViewOfFile(file_view);
+  return success;
+}
+
+// TODO(rkc): Work out if we want to handle NTFS junctions here or not, handle
+// them if we do decide to.
+bool IsLink(const FilePath& file_path) {
+  return false;
+}
+
+bool GetFileInfo(const FilePath& file_path, File::Info* results) {
+  WIN32_FILE_ATTRIBUTE_DATA attr;
+  if (!GetFileAttributesEx(ToWCharT(&file_path.value()), GetFileExInfoStandard,
+                           &attr)) {
+    return false;
+  }
+
+  ULARGE_INTEGER size;
+  size.HighPart = attr.nFileSizeHigh;
+  size.LowPart = attr.nFileSizeLow;
+  results->size = size.QuadPart;
+
+  results->is_directory =
+      (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+  results->last_modified = *reinterpret_cast<uint64_t*>(&attr.ftLastWriteTime);
+  results->last_accessed = *reinterpret_cast<uint64_t*>(&attr.ftLastAccessTime);
+  results->creation_time = *reinterpret_cast<uint64_t*>(&attr.ftCreationTime);
+
+  return true;
+}
+
+FILE* OpenFile(const FilePath& filename, const char* mode) {
+  // 'N' is unconditionally added below, so be sure there is not one already
+  // present before a comma in |mode|.
+  DCHECK(
+      strchr(mode, 'N') == nullptr ||
+      (strchr(mode, ',') != nullptr && strchr(mode, 'N') > strchr(mode, ',')));
+  std::u16string w_mode = ASCIIToUTF16(mode);
+  AppendModeCharacter(L'N', &w_mode);
+  return _wfsopen(ToWCharT(&filename.value()), ToWCharT(&w_mode), _SH_DENYNO);
+}
+
+FILE* FileToFILE(File file, const char* mode) {
+  if (!file.IsValid())
+    return NULL;
+  int fd =
+      _open_osfhandle(reinterpret_cast<intptr_t>(file.GetPlatformFile()), 0);
+  if (fd < 0)
+    return NULL;
+  file.TakePlatformFile();
+  FILE* stream = _fdopen(fd, mode);
+  if (!stream)
+    _close(fd);
+  return stream;
+}
+
+int ReadFile(const FilePath& filename, char* data, int max_size) {
+  win::ScopedHandle file(CreateFile(ToWCharT(&filename.value()), GENERIC_READ,
+                                    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+                                    OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,
+                                    NULL));
+  if (!file.IsValid())
+    return -1;
+
+  DWORD read;
+  if (::ReadFile(file.Get(), data, max_size, &read, NULL))
+    return read;
+
+  return -1;
+}
+
+int WriteFile(const FilePath& filename, const char* data, int size) {
+  win::ScopedHandle file(CreateFile(ToWCharT(&filename.value()), GENERIC_WRITE,
+                                    0, NULL, CREATE_ALWAYS,
+                                    FILE_ATTRIBUTE_NORMAL, NULL));
+  if (!file.IsValid()) {
+    DPLOG(WARNING) << "CreateFile failed for path "
+                   << UTF16ToUTF8(filename.value());
+    return -1;
+  }
+
+  DWORD written;
+  BOOL result = ::WriteFile(file.Get(), data, size, &written, NULL);
+  if (result && static_cast<int>(written) == size)
+    return written;
+
+  if (!result) {
+    // WriteFile failed.
+    DPLOG(WARNING) << "writing file " << UTF16ToUTF8(filename.value())
+                   << " failed";
+  } else {
+    // Didn't write all the bytes.
+    DLOG(WARNING) << "wrote" << written << " bytes to "
+                  << UTF16ToUTF8(filename.value()) << " expected " << size;
+  }
+  return -1;
+}
+
+bool AppendToFile(const FilePath& filename, const char* data, int size) {
+  win::ScopedHandle file(CreateFile(ToWCharT(&filename.value()),
+                                    FILE_APPEND_DATA, 0, NULL, OPEN_EXISTING, 0,
+                                    NULL));
+  if (!file.IsValid()) {
+    return false;
+  }
+
+  DWORD written;
+  BOOL result = ::WriteFile(file.Get(), data, size, &written, NULL);
+  if (result && static_cast<int>(written) == size)
+    return true;
+
+  return false;
+}
+
+bool GetCurrentDirectory(FilePath* dir) {
+  char16_t system_buffer[MAX_PATH];
+  system_buffer[0] = 0;
+  DWORD len = ::GetCurrentDirectory(MAX_PATH, ToWCharT(system_buffer));
+  if (len == 0 || len > MAX_PATH)
+    return false;
+  // TODO(evanm): the old behavior of this function was to always strip the
+  // trailing slash.  We duplicate this here, but it shouldn't be necessary
+  // when everyone is using the appropriate FilePath APIs.
+  std::u16string dir_str(system_buffer);
+  *dir = FilePath(dir_str).StripTrailingSeparators();
+  return true;
+}
+
+bool SetCurrentDirectory(const FilePath& directory) {
+  return ::SetCurrentDirectory(ToWCharT(&directory.value())) != 0;
+}
+
+int GetMaximumPathComponentLength(const FilePath& path) {
+  char16_t volume_path[MAX_PATH];
+  if (!GetVolumePathNameW(ToWCharT(&path.NormalizePathSeparators().value()),
+                          ToWCharT(volume_path), std::size(volume_path))) {
+    return -1;
+  }
+
+  DWORD max_length = 0;
+  if (!GetVolumeInformationW(ToWCharT(volume_path), NULL, 0, NULL, &max_length,
+                             NULL, NULL, 0)) {
+    return -1;
+  }
+
+  // Length of |path| with path separator appended.
+  size_t prefix = path.StripTrailingSeparators().value().size() + 1;
+  // The whole path string must be shorter than MAX_PATH. That is, it must be
+  // prefix + component_length < MAX_PATH (or equivalently, <= MAX_PATH - 1).
+  int whole_path_limit = std::max(0, MAX_PATH - 1 - static_cast<int>(prefix));
+  return std::min(whole_path_limit, static_cast<int>(max_length));
+}
+
+bool SetNonBlocking(int fd) {
+  unsigned long nonblocking = 1;
+  if (ioctlsocket(fd, FIONBIO, &nonblocking) == 0)
+    return true;
+  return false;
+}
+
+}  // namespace base
diff --git a/src/base/files/file_win.cc b/src/base/files/file_win.cc
new file mode 100644 (file)
index 0000000..e9c90e3
--- /dev/null
@@ -0,0 +1,324 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file.h"
+
+#include <io.h>
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "base/win/win_util.h"
+
+#include <windows.h>
+
+namespace base {
+
+// Make sure our Whence mappings match the system headers.
+static_assert(File::FROM_BEGIN == FILE_BEGIN &&
+                  File::FROM_CURRENT == FILE_CURRENT &&
+                  File::FROM_END == FILE_END,
+              "whence mapping must match the system headers");
+
+bool File::IsValid() const {
+  return file_.IsValid();
+}
+
+PlatformFile File::GetPlatformFile() const {
+  return file_.Get();
+}
+
+PlatformFile File::TakePlatformFile() {
+  return file_.Take();
+}
+
+void File::Close() {
+  if (!file_.IsValid())
+    return;
+
+  file_.Close();
+}
+
+int64_t File::Seek(Whence whence, int64_t offset) {
+  DCHECK(IsValid());
+
+  LARGE_INTEGER distance, res;
+  distance.QuadPart = offset;
+  DWORD move_method = static_cast<DWORD>(whence);
+  if (!SetFilePointerEx(file_.Get(), distance, &res, move_method))
+    return -1;
+  return res.QuadPart;
+}
+
+int File::Read(int64_t offset, char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  LARGE_INTEGER offset_li;
+  offset_li.QuadPart = offset;
+
+  OVERLAPPED overlapped = {};
+  overlapped.Offset = offset_li.LowPart;
+  overlapped.OffsetHigh = offset_li.HighPart;
+
+  DWORD bytes_read;
+  if (::ReadFile(file_.Get(), data, size, &bytes_read, &overlapped))
+    return bytes_read;
+  if (ERROR_HANDLE_EOF == GetLastError())
+    return 0;
+
+  return -1;
+}
+
+int File::ReadAtCurrentPos(char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  DWORD bytes_read;
+  if (::ReadFile(file_.Get(), data, size, &bytes_read, NULL))
+    return bytes_read;
+  if (ERROR_HANDLE_EOF == GetLastError())
+    return 0;
+
+  return -1;
+}
+
+int File::ReadNoBestEffort(int64_t offset, char* data, int size) {
+  // TODO(dbeam): trace this separately?
+  return Read(offset, data, size);
+}
+
+int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
+  // TODO(dbeam): trace this separately?
+  return ReadAtCurrentPos(data, size);
+}
+
+int File::Write(int64_t offset, const char* data, int size) {
+  DCHECK(IsValid());
+
+  LARGE_INTEGER offset_li;
+  offset_li.QuadPart = offset;
+
+  OVERLAPPED overlapped = {};
+  overlapped.Offset = offset_li.LowPart;
+  overlapped.OffsetHigh = offset_li.HighPart;
+
+  DWORD bytes_written;
+  if (::WriteFile(file_.Get(), data, size, &bytes_written, &overlapped))
+    return bytes_written;
+
+  return -1;
+}
+
+int File::WriteAtCurrentPos(const char* data, int size) {
+  DCHECK(IsValid());
+  if (size < 0)
+    return -1;
+
+  DWORD bytes_written;
+  if (::WriteFile(file_.Get(), data, size, &bytes_written, NULL))
+    return bytes_written;
+
+  return -1;
+}
+
+int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
+  return WriteAtCurrentPos(data, size);
+}
+
+int64_t File::GetLength() {
+  DCHECK(IsValid());
+
+  LARGE_INTEGER size;
+  if (!::GetFileSizeEx(file_.Get(), &size))
+    return -1;
+
+  return static_cast<int64_t>(size.QuadPart);
+}
+
+bool File::SetLength(int64_t length) {
+  DCHECK(IsValid());
+
+  // Get the current file pointer.
+  LARGE_INTEGER file_pointer;
+  LARGE_INTEGER zero;
+  zero.QuadPart = 0;
+  if (!::SetFilePointerEx(file_.Get(), zero, &file_pointer, FILE_CURRENT))
+    return false;
+
+  LARGE_INTEGER length_li;
+  length_li.QuadPart = length;
+  // If length > file size, SetFilePointerEx() should extend the file
+  // with zeroes on all Windows standard file systems (NTFS, FATxx).
+  if (!::SetFilePointerEx(file_.Get(), length_li, NULL, FILE_BEGIN))
+    return false;
+
+  // Set the new file length and move the file pointer to its old position.
+  // This is consistent with ftruncate()'s behavior, even when the file
+  // pointer points to a location beyond the end of the file.
+  // TODO(rvargas): Emulating ftruncate details seem suspicious and it is not
+  // promised by the interface (nor was promised by PlatformFile). See if this
+  // implementation detail can be removed.
+  return ((::SetEndOfFile(file_.Get()) != FALSE) &&
+          (::SetFilePointerEx(file_.Get(), file_pointer, NULL, FILE_BEGIN) !=
+           FALSE));
+}
+
+bool File::GetInfo(Info* info) {
+  DCHECK(IsValid());
+
+  BY_HANDLE_FILE_INFORMATION file_info;
+  if (!GetFileInformationByHandle(file_.Get(), &file_info))
+    return false;
+
+  LARGE_INTEGER size;
+  size.HighPart = file_info.nFileSizeHigh;
+  size.LowPart = file_info.nFileSizeLow;
+  info->size = size.QuadPart;
+  info->is_directory =
+      (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+  info->is_symbolic_link = false;  // Windows doesn't have symbolic links.
+  info->last_modified =
+      *reinterpret_cast<uint64_t*>(&file_info.ftLastWriteTime);
+  info->last_accessed =
+      *reinterpret_cast<uint64_t*>(&file_info.ftLastAccessTime);
+  info->creation_time = *reinterpret_cast<uint64_t*>(&file_info.ftCreationTime);
+  return true;
+}
+
+File::Error File::Lock() {
+  DCHECK(IsValid());
+
+  BOOL result = LockFile(file_.Get(), 0, 0, MAXDWORD, MAXDWORD);
+  if (!result)
+    return GetLastFileError();
+  return FILE_OK;
+}
+
+File::Error File::Unlock() {
+  DCHECK(IsValid());
+
+  BOOL result = UnlockFile(file_.Get(), 0, 0, MAXDWORD, MAXDWORD);
+  if (!result)
+    return GetLastFileError();
+  return FILE_OK;
+}
+
+File File::Duplicate() const {
+  if (!IsValid())
+    return File();
+
+  HANDLE other_handle = nullptr;
+
+  if (!::DuplicateHandle(GetCurrentProcess(),  // hSourceProcessHandle
+                         GetPlatformFile(),
+                         GetCurrentProcess(),  // hTargetProcessHandle
+                         &other_handle,
+                         0,      // dwDesiredAccess ignored due to SAME_ACCESS
+                         FALSE,  // !bInheritHandle
+                         DUPLICATE_SAME_ACCESS)) {
+    return File(GetLastFileError());
+  }
+
+  File other(other_handle);
+  return other;
+}
+
+// Static.
+File::Error File::OSErrorToFileError(DWORD last_error) {
+  switch (last_error) {
+    case ERROR_SHARING_VIOLATION:
+      return FILE_ERROR_IN_USE;
+    case ERROR_ALREADY_EXISTS:
+    case ERROR_FILE_EXISTS:
+      return FILE_ERROR_EXISTS;
+    case ERROR_FILE_NOT_FOUND:
+    case ERROR_PATH_NOT_FOUND:
+      return FILE_ERROR_NOT_FOUND;
+    case ERROR_ACCESS_DENIED:
+      return FILE_ERROR_ACCESS_DENIED;
+    case ERROR_TOO_MANY_OPEN_FILES:
+      return FILE_ERROR_TOO_MANY_OPENED;
+    case ERROR_OUTOFMEMORY:
+    case ERROR_NOT_ENOUGH_MEMORY:
+      return FILE_ERROR_NO_MEMORY;
+    case ERROR_HANDLE_DISK_FULL:
+    case ERROR_DISK_FULL:
+#ifndef __MINGW32__
+    case ERROR_DISK_RESOURCES_EXHAUSTED:
+#endif
+      return FILE_ERROR_NO_SPACE;
+    case ERROR_USER_MAPPED_FILE:
+      return FILE_ERROR_INVALID_OPERATION;
+    case ERROR_NOT_READY:
+    case ERROR_SECTOR_NOT_FOUND:
+    case ERROR_DEV_NOT_EXIST:
+    case ERROR_IO_DEVICE:
+    case ERROR_FILE_CORRUPT:
+    case ERROR_DISK_CORRUPT:
+      return FILE_ERROR_IO;
+    default:
+      // This function should only be called for errors.
+      DCHECK_NE(static_cast<DWORD>(ERROR_SUCCESS), last_error);
+      return FILE_ERROR_FAILED;
+  }
+}
+
+void File::DoInitialize(const FilePath& path, uint32_t flags) {
+  DCHECK(!IsValid());
+
+  DWORD disposition = 0;
+
+  if (flags & FLAG_OPEN)
+    disposition = OPEN_EXISTING;
+
+  if (flags & FLAG_CREATE_ALWAYS) {
+    DCHECK(!disposition);
+    DCHECK(flags & FLAG_WRITE);
+    disposition = CREATE_ALWAYS;
+  }
+
+  if (!disposition) {
+    ::SetLastError(ERROR_INVALID_PARAMETER);
+    error_details_ = FILE_ERROR_FAILED;
+    NOTREACHED();
+    return;
+  }
+
+  DWORD access = 0;
+  if (flags & FLAG_WRITE)
+    access = GENERIC_WRITE;
+  if (flags & FLAG_READ)
+    access |= GENERIC_READ;
+
+  DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+  DWORD create_flags = 0;
+  file_.Set(CreateFile(ToWCharT(&path.value()), access, sharing, NULL,
+                       disposition, create_flags, NULL));
+
+  if (file_.IsValid()) {
+    error_details_ = FILE_OK;
+    if (flags & FLAG_CREATE_ALWAYS)
+      created_ = true;
+  } else {
+    error_details_ = GetLastFileError();
+  }
+}
+
+bool File::Flush() {
+  DCHECK(IsValid());
+  return ::FlushFileBuffers(file_.Get()) != FALSE;
+}
+
+void File::SetPlatformFile(PlatformFile file) {
+  file_.Set(file);
+}
+
+// static
+File::Error File::GetLastFileError() {
+  return File::OSErrorToFileError(GetLastError());
+}
+
+}  // namespace base
diff --git a/src/base/files/platform_file.h b/src/base/files/platform_file.h
new file mode 100644 (file)
index 0000000..67c2d30
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_PLATFORM_FILE_H_
+#define BASE_FILES_PLATFORM_FILE_H_
+
+#include "base/files/scoped_file.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#include <windows.h>
+#endif
+
+// This file defines platform-independent types for dealing with
+// platform-dependent files. If possible, use the higher-level base::File class
+// rather than these primitives.
+
+namespace base {
+
+#if defined(OS_WIN)
+
+using PlatformFile = HANDLE;
+using ScopedPlatformFile = ::base::win::ScopedHandle;
+
+// It would be nice to make this constexpr but INVALID_HANDLE_VALUE is a
+// ((void*)(-1)) which Clang rejects since reinterpret_cast is technically
+// disallowed in constexpr. Visual Studio accepts this, however.
+const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+using PlatformFile = int;
+using ScopedPlatformFile = ::base::ScopedFD;
+
+constexpr PlatformFile kInvalidPlatformFile = -1;
+
+#endif
+
+}  // namespace base
+
+#endif  // BASE_FILES_PLATFORM_FILE_H_
diff --git a/src/base/files/scoped_file.cc b/src/base/files/scoped_file.cc
new file mode 100644 (file)
index 0000000..1b8733e
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_file.h"
+
+#include "base/logging.h"
+#include "util/build_config.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#endif
+
+namespace base {
+namespace internal {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+// static
+void ScopedFDCloseTraits::Free(int fd) {
+  // It's important to crash here.
+  // There are security implications to not closing a file descriptor
+  // properly. As file descriptors are "capabilities", keeping them open
+  // would make the current process keep access to a resource. Much of
+  // Chrome relies on being able to "drop" such access.
+  // It's especially problematic on Linux with the setuid sandbox, where
+  // a single open directory would bypass the entire security model.
+  int ret = IGNORE_EINTR(close(fd));
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FUCHSIA) || \
+    defined(OS_ANDROID) || defined(OS_BSD)
+  // NB: Some file descriptors can return errors from close() e.g. network
+  // filesystems such as NFS and Linux input devices. On Linux, macOS, and
+  // Fuchsia's POSIX layer, errors from close other than EBADF do not indicate
+  // failure to actually close the fd.
+  if (ret != 0 && errno != EBADF)
+    ret = 0;
+#endif
+
+  PCHECK(0 == ret);
+}
+
+#endif  // OS_POSIX || OS_FUCHSIA
+
+}  // namespace internal
+}  // namespace base
diff --git a/src/base/files/scoped_file.h b/src/base/files/scoped_file.h
new file mode 100644 (file)
index 0000000..0d89650
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_SCOPED_FILE_H_
+#define BASE_FILES_SCOPED_FILE_H_
+
+#include <stdio.h>
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/scoped_generic.h"
+#include "util/build_config.h"
+
+namespace base {
+
+namespace internal {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+struct ScopedFDCloseTraits {
+  static int InvalidValue() { return -1; }
+  static void Free(int fd);
+};
+#endif
+
+// Functor for |ScopedFILE| (below).
+struct ScopedFILECloser {
+  inline void operator()(FILE* x) const {
+    if (x)
+      fclose(x);
+  }
+};
+
+}  // namespace internal
+
+// -----------------------------------------------------------------------------
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+// A low-level Posix file descriptor closer class. Use this when writing
+// platform-specific code, especially that does non-file-like things with the
+// FD (like sockets).
+//
+// If you're writing low-level Windows code, see base/win/scoped_handle.h
+// which provides some additional functionality.
+//
+// If you're writing cross-platform code that deals with actual files, you
+// should generally use base::File instead which can be constructed with a
+// handle, and in addition to handling ownership, has convenient cross-platform
+// file manipulation functions on it.
+typedef ScopedGeneric<int, internal::ScopedFDCloseTraits> ScopedFD;
+#endif
+
+// Automatically closes |FILE*|s.
+typedef std::unique_ptr<FILE, internal::ScopedFILECloser> ScopedFILE;
+
+}  // namespace base
+
+#endif  // BASE_FILES_SCOPED_FILE_H_
diff --git a/src/base/files/scoped_temp_dir.cc b/src/base/files/scoped_temp_dir.cc
new file mode 100644 (file)
index 0000000..01ec0f0
--- /dev/null
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_temp_dir.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+
+namespace base {
+
+namespace {
+
+constexpr FilePath::CharType kScopedDirPrefix[] =
+    FILE_PATH_LITERAL("scoped_dir");
+
+}  // namespace
+
+ScopedTempDir::ScopedTempDir() = default;
+
+ScopedTempDir::~ScopedTempDir() {
+  if (!path_.empty() && !Delete())
+    DLOG(WARNING) << "Could not delete temp dir in dtor.";
+}
+
+bool ScopedTempDir::CreateUniqueTempDir() {
+  if (!path_.empty())
+    return false;
+
+  // This "scoped_dir" prefix is only used on Windows and serves as a template
+  // for the unique name.
+  if (!base::CreateNewTempDirectory(kScopedDirPrefix, &path_))
+    return false;
+
+  return true;
+}
+
+bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) {
+  if (!path_.empty())
+    return false;
+
+  // If |base_path| does not exist, create it.
+  if (!base::CreateDirectory(base_path))
+    return false;
+
+  // Create a new, uniquely named directory under |base_path|.
+  if (!base::CreateTemporaryDirInDir(base_path, kScopedDirPrefix, &path_))
+    return false;
+
+  return true;
+}
+
+bool ScopedTempDir::Set(const FilePath& path) {
+  if (!path_.empty())
+    return false;
+
+  if (!DirectoryExists(path) && !base::CreateDirectory(path))
+    return false;
+
+  path_ = path;
+  return true;
+}
+
+bool ScopedTempDir::Delete() {
+  if (path_.empty())
+    return false;
+
+  bool ret = base::DeleteFile(path_, true);
+  if (ret) {
+    // We only clear the path if deleted the directory.
+    path_.clear();
+  }
+
+  return ret;
+}
+
+FilePath ScopedTempDir::Take() {
+  FilePath ret = path_;
+  path_ = FilePath();
+  return ret;
+}
+
+const FilePath& ScopedTempDir::GetPath() const {
+  DCHECK(!path_.empty()) << "Did you call CreateUniqueTempDir* before?";
+  return path_;
+}
+
+bool ScopedTempDir::IsValid() const {
+  return !path_.empty() && DirectoryExists(path_);
+}
+
+// static
+const FilePath::CharType* ScopedTempDir::GetTempDirPrefix() {
+  return kScopedDirPrefix;
+}
+
+}  // namespace base
diff --git a/src/base/files/scoped_temp_dir.h b/src/base/files/scoped_temp_dir.h
new file mode 100644 (file)
index 0000000..4ddb690
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_SCOPED_TEMP_DIR_H_
+#define BASE_FILES_SCOPED_TEMP_DIR_H_
+
+// An object representing a temporary / scratch directory that should be
+// cleaned up (recursively) when this object goes out of scope.  Since deletion
+// occurs during the destructor, no further error handling is possible if the
+// directory fails to be deleted.  As a result, deletion is not guaranteed by
+// this class.  (However note that, whenever possible, by default
+// CreateUniqueTempDir creates the directory in a location that is
+// automatically cleaned up on reboot, or at other appropriate times.)
+//
+// Multiple calls to the methods which establish a temporary directory
+// (CreateUniqueTempDir, CreateUniqueTempDirUnderPath, and Set) must have
+// intervening calls to Delete or Take, or the calls will fail.
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+
+namespace base {
+
+class ScopedTempDir {
+ public:
+  // No directory is owned/created initially.
+  ScopedTempDir();
+
+  // Recursively delete path.
+  ~ScopedTempDir();
+
+  // Creates a unique directory in TempPath, and takes ownership of it.
+  // See file_util::CreateNewTemporaryDirectory.
+  bool CreateUniqueTempDir() WARN_UNUSED_RESULT;
+
+  // Creates a unique directory under a given path, and takes ownership of it.
+  bool CreateUniqueTempDirUnderPath(const FilePath& path) WARN_UNUSED_RESULT;
+
+  // Takes ownership of directory at |path|, creating it if necessary.
+  // Don't call multiple times unless Take() has been called first.
+  bool Set(const FilePath& path) WARN_UNUSED_RESULT;
+
+  // Deletes the temporary directory wrapped by this object.
+  bool Delete() WARN_UNUSED_RESULT;
+
+  // Caller takes ownership of the temporary directory so it won't be destroyed
+  // when this object goes out of scope.
+  FilePath Take();
+
+  // Returns the path to the created directory. Call one of the
+  // CreateUniqueTempDir* methods before getting the path.
+  const FilePath& GetPath() const;
+
+  // Returns true if path_ is non-empty and exists.
+  bool IsValid() const;
+
+  // Returns the prefix used for temp directory names generated by
+  // ScopedTempDirs.
+  static const FilePath::CharType* GetTempDirPrefix();
+
+ private:
+  FilePath path_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedTempDir);
+};
+
+}  // namespace base
+
+#endif  // BASE_FILES_SCOPED_TEMP_DIR_H_
diff --git a/src/base/gtest_prod_util.h b/src/base/gtest_prod_util.h
new file mode 100644 (file)
index 0000000..b3b1175
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_GTEST_PROD_UTIL_H_
+#define BASE_GTEST_PROD_UTIL_H_
+
+// TODO: Remove me.
+#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \
+  friend struct test_case_name##test_name
+
+#endif  // BASE_GTEST_PROD_UTIL_H_
diff --git a/src/base/json/json_parser.cc b/src/base/json/json_parser.cc
new file mode 100644 (file)
index 0000000..df02829
--- /dev/null
@@ -0,0 +1,747 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_parser.h"
+
+#include <cmath>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "base/values.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+const int32_t kExtendedASCIIStart = 0x80;
+
+// Simple class that checks for maximum recursion/"stack overflow."
+class StackMarker {
+ public:
+  StackMarker(int max_depth, int* depth)
+      : max_depth_(max_depth), depth_(depth) {
+    ++(*depth_);
+    DCHECK_LE(*depth_, max_depth_);
+  }
+  ~StackMarker() { --(*depth_); }
+
+  bool IsTooDeep() const { return *depth_ >= max_depth_; }
+
+ private:
+  const int max_depth_;
+  int* const depth_;
+
+  DISALLOW_COPY_AND_ASSIGN(StackMarker);
+};
+
+constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD;
+
+}  // namespace
+
+// This is U+FFFD.
+const char kUnicodeReplacementString[] = "\xEF\xBF\xBD";
+
+JSONParser::JSONParser(int options, int max_depth)
+    : options_(options),
+      max_depth_(max_depth),
+      index_(0),
+      stack_depth_(0),
+      line_number_(0),
+      index_last_line_(0),
+      error_code_(JSONReader::JSON_NO_ERROR),
+      error_line_(0),
+      error_column_(0) {
+  CHECK_LE(max_depth, JSONReader::kStackMaxDepth);
+}
+
+JSONParser::~JSONParser() = default;
+
+std::optional<Value> JSONParser::Parse(std::string_view input) {
+  input_ = input;
+  index_ = 0;
+  line_number_ = 1;
+  index_last_line_ = 0;
+
+  error_code_ = JSONReader::JSON_NO_ERROR;
+  error_line_ = 0;
+  error_column_ = 0;
+
+  // ICU and ReadUnicodeCharacter() use int32_t for lengths, so ensure
+  // that the index_ will not overflow when parsing.
+  if (!base::IsValueInRangeForNumericType<int32_t>(input.length())) {
+    ReportError(JSONReader::JSON_TOO_LARGE, 0);
+    return std::nullopt;
+  }
+
+  // When the input JSON string starts with a UTF-8 Byte-Order-Mark,
+  // advance the start position to avoid the ParseNextToken function mis-
+  // treating a Unicode BOM as an invalid character and returning NULL.
+  ConsumeIfMatch("\xEF\xBB\xBF");
+
+  // Parse the first and any nested tokens.
+  std::optional<Value> root(ParseNextToken());
+  if (!root)
+    return std::nullopt;
+
+  // Make sure the input stream is at an end.
+  if (GetNextToken() != T_END_OF_INPUT) {
+    ReportError(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, 1);
+    return std::nullopt;
+  }
+
+  return root;
+}
+
+JSONReader::JsonParseError JSONParser::error_code() const {
+  return error_code_;
+}
+
+std::string JSONParser::GetErrorMessage() const {
+  return FormatErrorMessage(error_line_, error_column_,
+                            JSONReader::ErrorCodeToString(error_code_));
+}
+
+int JSONParser::error_line() const {
+  return error_line_;
+}
+
+int JSONParser::error_column() const {
+  return error_column_;
+}
+
+// StringBuilder ///////////////////////////////////////////////////////////////
+
+JSONParser::StringBuilder::StringBuilder() : StringBuilder(nullptr) {}
+
+JSONParser::StringBuilder::StringBuilder(const char* pos)
+    : pos_(pos), length_(0) {}
+
+JSONParser::StringBuilder::~StringBuilder() = default;
+
+JSONParser::StringBuilder& JSONParser::StringBuilder::operator=(
+    StringBuilder&& other) = default;
+
+void JSONParser::StringBuilder::Append(uint32_t point) {
+  DCHECK(IsValidCharacter(point));
+
+  if (point < kExtendedASCIIStart && !string_) {
+    DCHECK_EQ(static_cast<char>(point), pos_[length_]);
+    ++length_;
+  } else {
+    Convert();
+    if (UNLIKELY(point == kUnicodeReplacementPoint)) {
+      string_->append(kUnicodeReplacementString);
+    } else {
+      WriteUnicodeCharacter(point, &*string_);
+    }
+  }
+}
+
+void JSONParser::StringBuilder::Convert() {
+  if (string_)
+    return;
+  string_.emplace(pos_, length_);
+}
+
+std::string JSONParser::StringBuilder::DestructiveAsString() {
+  if (string_)
+    return std::move(*string_);
+  return std::string(pos_, length_);
+}
+
+// JSONParser private //////////////////////////////////////////////////////////
+
+std::optional<std::string_view> JSONParser::PeekChars(int count) {
+  if (static_cast<size_t>(index_) + count > input_.length())
+    return std::nullopt;
+  // Using std::string_view::substr() is significantly slower (according to
+  // base_perftests) than constructing a substring manually.
+  return std::string_view(input_.data() + index_, count);
+}
+
+std::optional<char> JSONParser::PeekChar() {
+  std::optional<std::string_view> chars = PeekChars(1);
+  if (chars)
+    return (*chars)[0];
+  return std::nullopt;
+}
+
+std::optional<std::string_view> JSONParser::ConsumeChars(int count) {
+  std::optional<std::string_view> chars = PeekChars(count);
+  if (chars)
+    index_ += count;
+  return chars;
+}
+
+std::optional<char> JSONParser::ConsumeChar() {
+  std::optional<std::string_view> chars = ConsumeChars(1);
+  if (chars)
+    return (*chars)[0];
+  return std::nullopt;
+}
+
+const char* JSONParser::pos() {
+  CHECK_LE(static_cast<size_t>(index_), input_.length());
+  return input_.data() + index_;
+}
+
+JSONParser::Token JSONParser::GetNextToken() {
+  EatWhitespaceAndComments();
+
+  std::optional<char> c = PeekChar();
+  if (!c)
+    return T_END_OF_INPUT;
+
+  switch (*c) {
+    case '{':
+      return T_OBJECT_BEGIN;
+    case '}':
+      return T_OBJECT_END;
+    case '[':
+      return T_ARRAY_BEGIN;
+    case ']':
+      return T_ARRAY_END;
+    case '"':
+      return T_STRING;
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case '-':
+      return T_NUMBER;
+    case 't':
+      return T_BOOL_TRUE;
+    case 'f':
+      return T_BOOL_FALSE;
+    case 'n':
+      return T_NULL;
+    case ',':
+      return T_LIST_SEPARATOR;
+    case ':':
+      return T_OBJECT_PAIR_SEPARATOR;
+    default:
+      return T_INVALID_TOKEN;
+  }
+}
+
+void JSONParser::EatWhitespaceAndComments() {
+  while (std::optional<char> c = PeekChar()) {
+    switch (*c) {
+      case '\r':
+      case '\n':
+        index_last_line_ = index_;
+        // Don't increment line_number_ twice for "\r\n".
+        if (!(c == '\n' && index_ > 0 && input_[index_ - 1] == '\r')) {
+          ++line_number_;
+        }
+        FALLTHROUGH;
+      case ' ':
+      case '\t':
+        ConsumeChar();
+        break;
+      case '/':
+        if (!EatComment())
+          return;
+        break;
+      default:
+        return;
+    }
+  }
+}
+
+bool JSONParser::EatComment() {
+  std::optional<std::string_view> comment_start = ConsumeChars(2);
+  if (!comment_start)
+    return false;
+
+  if (comment_start == "//") {
+    // Single line comment, read to newline.
+    while (std::optional<char> c = PeekChar()) {
+      if (c == '\n' || c == '\r')
+        return true;
+      ConsumeChar();
+    }
+  } else if (comment_start == "/*") {
+    char previous_char = '\0';
+    // Block comment, read until end marker.
+    while (std::optional<char> c = PeekChar()) {
+      if (previous_char == '*' && c == '/') {
+        // EatWhitespaceAndComments will inspect pos(), which will still be on
+        // the last / of the comment, so advance once more (which may also be
+        // end of input).
+        ConsumeChar();
+        return true;
+      }
+      previous_char = *ConsumeChar();
+    }
+
+    // If the comment is unterminated, GetNextToken will report T_END_OF_INPUT.
+  }
+
+  return false;
+}
+
+std::optional<Value> JSONParser::ParseNextToken() {
+  return ParseToken(GetNextToken());
+}
+
+std::optional<Value> JSONParser::ParseToken(Token token) {
+  switch (token) {
+    case T_OBJECT_BEGIN:
+      return ConsumeDictionary();
+    case T_ARRAY_BEGIN:
+      return ConsumeList();
+    case T_STRING:
+      return ConsumeString();
+    case T_NUMBER:
+      return ConsumeNumber();
+    case T_BOOL_TRUE:
+    case T_BOOL_FALSE:
+    case T_NULL:
+      return ConsumeLiteral();
+    default:
+      ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
+      return std::nullopt;
+  }
+}
+
+std::optional<Value> JSONParser::ConsumeDictionary() {
+  if (ConsumeChar() != '{') {
+    ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
+    return std::nullopt;
+  }
+
+  StackMarker depth_check(max_depth_, &stack_depth_);
+  if (depth_check.IsTooDeep()) {
+    ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 0);
+    return std::nullopt;
+  }
+
+  std::vector<Value::DictStorage::value_type> dict_storage;
+
+  Token token = GetNextToken();
+  while (token != T_OBJECT_END) {
+    if (token != T_STRING) {
+      ReportError(JSONReader::JSON_UNQUOTED_DICTIONARY_KEY, 1);
+      return std::nullopt;
+    }
+
+    // First consume the key.
+    StringBuilder key;
+    if (!ConsumeStringRaw(&key)) {
+      return std::nullopt;
+    }
+
+    // Read the separator.
+    token = GetNextToken();
+    if (token != T_OBJECT_PAIR_SEPARATOR) {
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+      return std::nullopt;
+    }
+
+    // The next token is the value. Ownership transfers to |dict|.
+    ConsumeChar();
+    std::optional<Value> value = ParseNextToken();
+    if (!value) {
+      // ReportError from deeper level.
+      return std::nullopt;
+    }
+
+    dict_storage.emplace_back(key.DestructiveAsString(),
+                              std::make_unique<Value>(std::move(*value)));
+
+    token = GetNextToken();
+    if (token == T_LIST_SEPARATOR) {
+      ConsumeChar();
+      token = GetNextToken();
+      if (token == T_OBJECT_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) {
+        ReportError(JSONReader::JSON_TRAILING_COMMA, 1);
+        return std::nullopt;
+      }
+    } else if (token != T_OBJECT_END) {
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 0);
+      return std::nullopt;
+    }
+  }
+
+  ConsumeChar();  // Closing '}'.
+
+  return Value(Value::DictStorage(std::move(dict_storage), KEEP_LAST_OF_DUPES));
+}
+
+std::optional<Value> JSONParser::ConsumeList() {
+  if (ConsumeChar() != '[') {
+    ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
+    return std::nullopt;
+  }
+
+  StackMarker depth_check(max_depth_, &stack_depth_);
+  if (depth_check.IsTooDeep()) {
+    ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 0);
+    return std::nullopt;
+  }
+
+  Value::ListStorage list_storage;
+
+  Token token = GetNextToken();
+  while (token != T_ARRAY_END) {
+    std::optional<Value> item = ParseToken(token);
+    if (!item) {
+      // ReportError from deeper level.
+      return std::nullopt;
+    }
+
+    list_storage.push_back(std::move(*item));
+
+    token = GetNextToken();
+    if (token == T_LIST_SEPARATOR) {
+      ConsumeChar();
+      token = GetNextToken();
+      if (token == T_ARRAY_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) {
+        ReportError(JSONReader::JSON_TRAILING_COMMA, 1);
+        return std::nullopt;
+      }
+    } else if (token != T_ARRAY_END) {
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+      return std::nullopt;
+    }
+  }
+
+  ConsumeChar();  // Closing ']'.
+
+  return Value(std::move(list_storage));
+}
+
+std::optional<Value> JSONParser::ConsumeString() {
+  StringBuilder string;
+  if (!ConsumeStringRaw(&string))
+    return std::nullopt;
+
+  return Value(string.DestructiveAsString());
+}
+
+bool JSONParser::ConsumeStringRaw(StringBuilder* out) {
+  if (ConsumeChar() != '"') {
+    ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
+    return false;
+  }
+
+  // StringBuilder will internally build a std::string_view unless a UTF-16
+  // conversion occurs, at which point it will perform a copy into a
+  // std::string.
+  StringBuilder string(pos());
+
+  while (PeekChar()) {
+    uint32_t next_char = 0;
+    if (!ReadUnicodeCharacter(input_.data(),
+                              static_cast<int32_t>(input_.length()), &index_,
+                              &next_char) ||
+        !IsValidCharacter(next_char)) {
+      if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) {
+        ReportError(JSONReader::JSON_UNSUPPORTED_ENCODING, 1);
+        return false;
+      }
+      ConsumeChar();
+      string.Append(kUnicodeReplacementPoint);
+      continue;
+    }
+
+    if (next_char == '"') {
+      ConsumeChar();
+      *out = std::move(string);
+      return true;
+    } else if (next_char != '\\') {
+      // If this character is not an escape sequence...
+      ConsumeChar();
+      string.Append(next_char);
+    } else {
+      // And if it is an escape sequence, the input string will be adjusted
+      // (either by combining the two characters of an encoded escape sequence,
+      // or with a UTF conversion), so using std::string_view isn't possible --
+      // force a conversion.
+      string.Convert();
+
+      // Read past the escape '\' and ensure there's a character following.
+      std::optional<std::string_view> escape_sequence = ConsumeChars(2);
+      if (!escape_sequence) {
+        ReportError(JSONReader::JSON_INVALID_ESCAPE, 0);
+        return false;
+      }
+
+      switch ((*escape_sequence)[1]) {
+        // Allowed esape sequences:
+        case 'x': {  // UTF-8 sequence.
+          // UTF-8 \x escape sequences are not allowed in the spec, but they
+          // are supported here for backwards-compatiblity with the old parser.
+          escape_sequence = ConsumeChars(2);
+          if (!escape_sequence) {
+            ReportError(JSONReader::JSON_INVALID_ESCAPE, -2);
+            return false;
+          }
+
+          int hex_digit = 0;
+          if (!HexStringToInt(*escape_sequence, &hex_digit) ||
+              !IsValidCharacter(hex_digit)) {
+            ReportError(JSONReader::JSON_INVALID_ESCAPE, -2);
+            return false;
+          }
+
+          string.Append(hex_digit);
+          break;
+        }
+        case 'u': {  // UTF-16 sequence.
+          // UTF units are of the form \uXXXX.
+          uint32_t code_point;
+          if (!DecodeUTF16(&code_point)) {
+            ReportError(JSONReader::JSON_INVALID_ESCAPE, 0);
+            return false;
+          }
+          string.Append(code_point);
+          break;
+        }
+        case '"':
+          string.Append('"');
+          break;
+        case '\\':
+          string.Append('\\');
+          break;
+        case '/':
+          string.Append('/');
+          break;
+        case 'b':
+          string.Append('\b');
+          break;
+        case 'f':
+          string.Append('\f');
+          break;
+        case 'n':
+          string.Append('\n');
+          break;
+        case 'r':
+          string.Append('\r');
+          break;
+        case 't':
+          string.Append('\t');
+          break;
+        case 'v':  // Not listed as valid escape sequence in the RFC.
+          string.Append('\v');
+          break;
+        // All other escape squences are illegal.
+        default:
+          ReportError(JSONReader::JSON_INVALID_ESCAPE, 0);
+          return false;
+      }
+    }
+  }
+
+  ReportError(JSONReader::JSON_SYNTAX_ERROR, 0);
+  return false;
+}
+
+// Entry is at the first X in \uXXXX.
+bool JSONParser::DecodeUTF16(uint32_t* out_code_point) {
+  std::optional<std::string_view> escape_sequence = ConsumeChars(4);
+  if (!escape_sequence)
+    return false;
+
+  // Consume the UTF-16 code unit, which may be a high surrogate.
+  int code_unit16_high = 0;
+  if (!HexStringToInt(*escape_sequence, &code_unit16_high))
+    return false;
+
+  // If this is a high surrogate, consume the next code unit to get the
+  // low surrogate.
+  if (CBU16_IS_SURROGATE(code_unit16_high)) {
+    // Make sure this is the high surrogate. If not, it's an encoding
+    // error.
+    if (!CBU16_IS_SURROGATE_LEAD(code_unit16_high))
+      return false;
+
+    // Make sure that the token has more characters to consume the
+    // lower surrogate.
+    if (!ConsumeIfMatch("\\u"))
+      return false;
+
+    escape_sequence = ConsumeChars(4);
+    if (!escape_sequence)
+      return false;
+
+    int code_unit16_low = 0;
+    if (!HexStringToInt(*escape_sequence, &code_unit16_low))
+      return false;
+
+    if (!CBU16_IS_TRAIL(code_unit16_low))
+      return false;
+
+    uint32_t code_point =
+        CBU16_GET_SUPPLEMENTARY(code_unit16_high, code_unit16_low);
+    if (!IsValidCharacter(code_point))
+      return false;
+
+    *out_code_point = code_point;
+  } else {
+    // Not a surrogate.
+    DCHECK(CBU16_IS_SINGLE(code_unit16_high));
+    if (!IsValidCharacter(code_unit16_high)) {
+      if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) {
+        return false;
+      }
+      *out_code_point = kUnicodeReplacementPoint;
+      return true;
+    }
+
+    *out_code_point = code_unit16_high;
+  }
+
+  return true;
+}
+
+std::optional<Value> JSONParser::ConsumeNumber() {
+  const char* num_start = pos();
+  const int start_index = index_;
+  int end_index = start_index;
+
+  if (PeekChar() == '-')
+    ConsumeChar();
+
+  if (!ReadInt(false)) {
+    ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+    return std::nullopt;
+  }
+  end_index = index_;
+
+  // The optional fraction part.
+  if (PeekChar() == '.') {
+    ConsumeChar();
+    if (!ReadInt(true)) {
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+      return std::nullopt;
+    }
+    end_index = index_;
+  }
+
+  // Optional exponent part.
+  std::optional<char> c = PeekChar();
+  if (c == 'e' || c == 'E') {
+    ConsumeChar();
+    if (PeekChar() == '-' || PeekChar() == '+') {
+      ConsumeChar();
+    }
+    if (!ReadInt(true)) {
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+      return std::nullopt;
+    }
+    end_index = index_;
+  }
+
+  // ReadInt is greedy because numbers have no easily detectable sentinel,
+  // so save off where the parser should be on exit (see Consume invariant at
+  // the top of the header), then make sure the next token is one which is
+  // valid.
+  int exit_index = index_;
+
+  switch (GetNextToken()) {
+    case T_OBJECT_END:
+    case T_ARRAY_END:
+    case T_LIST_SEPARATOR:
+    case T_END_OF_INPUT:
+      break;
+    default:
+      ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+      return std::nullopt;
+  }
+
+  index_ = exit_index;
+
+  std::string_view num_string(num_start, end_index - start_index);
+
+  int num_int;
+  if (StringToInt(num_string, &num_int))
+    return Value(num_int);
+
+  return std::nullopt;
+}
+
+bool JSONParser::ReadInt(bool allow_leading_zeros) {
+  size_t len = 0;
+  char first = 0;
+
+  while (std::optional<char> c = PeekChar()) {
+    if (!IsAsciiDigit(c))
+      break;
+
+    if (len == 0)
+      first = *c;
+
+    ++len;
+    ConsumeChar();
+  }
+
+  if (len == 0)
+    return false;
+
+  if (!allow_leading_zeros && len > 1 && first == '0')
+    return false;
+
+  return true;
+}
+
+std::optional<Value> JSONParser::ConsumeLiteral() {
+  if (ConsumeIfMatch("true")) {
+    return Value(true);
+  } else if (ConsumeIfMatch("false")) {
+    return Value(false);
+  } else if (ConsumeIfMatch("null")) {
+    return Value(Value::Type::NONE);
+  } else {
+    ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+    return std::nullopt;
+  }
+}
+
+bool JSONParser::ConsumeIfMatch(std::string_view match) {
+  if (match == PeekChars(match.size())) {
+    ConsumeChars(match.size());
+    return true;
+  }
+  return false;
+}
+
+void JSONParser::ReportError(JSONReader::JsonParseError code,
+                             int column_adjust) {
+  error_code_ = code;
+  error_line_ = line_number_;
+  error_column_ = index_ - index_last_line_ + column_adjust;
+}
+
+// static
+std::string JSONParser::FormatErrorMessage(int line,
+                                           int column,
+                                           const std::string& description) {
+  if (line || column) {
+    return StringPrintf("Line: %i, column: %i, %s", line, column,
+                        description.c_str());
+  }
+  return description;
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/src/base/json/json_parser.h b/src/base/json/json_parser.h
new file mode 100644 (file)
index 0000000..1001080
--- /dev/null
@@ -0,0 +1,260 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_JSON_JSON_PARSER_H_
+#define BASE_JSON_JSON_PARSER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+
+namespace base {
+
+class Value;
+
+namespace internal {
+
+class JSONParserTest;
+
+// The implementation behind the JSONReader interface. This class is not meant
+// to be used directly; it encapsulates logic that need not be exposed publicly.
+//
+// This parser guarantees O(n) time through the input string. Iteration happens
+// on the byte level, with the functions ConsumeChars() and ConsumeChar(). The
+// conversion from byte to JSON token happens without advancing the parser in
+// GetNextToken/ParseToken, that is tokenization operates on the current parser
+// position without advancing.
+//
+// Built on top of these are a family of Consume functions that iterate
+// internally. Invariant: on entry of a Consume function, the parser is wound
+// to the first byte of a valid JSON token. On exit, it is on the first byte
+// after the token that was just consumed, which would likely be the first byte
+// of the next token.
+class JSONParser {
+ public:
+  JSONParser(int options, int max_depth = JSONReader::kStackMaxDepth);
+  ~JSONParser();
+
+  // Parses the input string according to the set options and returns the
+  // result as a Value.
+  // Wrap this in base::FooValue::From() to check the Value is of type Foo and
+  // convert to a FooValue at the same time.
+  std::optional<Value> Parse(std::string_view input);
+
+  // Returns the error code.
+  JSONReader::JsonParseError error_code() const;
+
+  // Returns the human-friendly error message.
+  std::string GetErrorMessage() const;
+
+  // Returns the error line number if parse error happened. Otherwise always
+  // returns 0.
+  int error_line() const;
+
+  // Returns the error column number if parse error happened. Otherwise always
+  // returns 0.
+  int error_column() const;
+
+ private:
+  enum Token {
+    T_OBJECT_BEGIN,  // {
+    T_OBJECT_END,    // }
+    T_ARRAY_BEGIN,   // [
+    T_ARRAY_END,     // ]
+    T_STRING,
+    T_NUMBER,
+    T_BOOL_TRUE,              // true
+    T_BOOL_FALSE,             // false
+    T_NULL,                   // null
+    T_LIST_SEPARATOR,         // ,
+    T_OBJECT_PAIR_SEPARATOR,  // :
+    T_END_OF_INPUT,
+    T_INVALID_TOKEN,
+  };
+
+  // A helper class used for parsing strings. One optimization performed is to
+  // create base::Value with a std::string_view to avoid unnecessary std::string
+  // copies. This is not possible if the input string needs to be decoded from
+  // UTF-16 to UTF-8, or if an escape sequence causes characters to be skipped.
+  // This class centralizes that logic.
+  class StringBuilder {
+   public:
+    // Empty constructor. Used for creating a builder with which to assign to.
+    StringBuilder();
+
+    // |pos| is the beginning of an input string, excluding the |"|.
+    explicit StringBuilder(const char* pos);
+
+    ~StringBuilder();
+
+    StringBuilder& operator=(StringBuilder&& other);
+
+    // Appends the Unicode code point |point| to the string, either by
+    // increasing the |length_| of the string if the string has not been
+    // converted, or by appending the UTF8 bytes for the code point.
+    void Append(uint32_t point);
+
+    // Converts the builder from its default std::string_view to a full
+    // std::string, performing a copy. Once a builder is converted, it cannot be
+    // made a std::string_view again.
+    void Convert();
+
+    // Returns the builder as a string, invalidating all state. This allows
+    // the internal string buffer representation to be destructively moved
+    // in cases where the builder will not be needed any more.
+    std::string DestructiveAsString();
+
+   private:
+    // The beginning of the input string.
+    const char* pos_;
+
+    // Number of bytes in |pos_| that make up the string being built.
+    size_t length_;
+
+    // The copied string representation. Will be unset until Convert() is
+    // called.
+    std::optional<std::string> string_;
+  };
+
+  // Returns the next |count| bytes of the input stream, or nullopt if fewer
+  // than |count| bytes remain.
+  std::optional<std::string_view> PeekChars(int count);
+
+  // Calls PeekChars() with a |count| of 1.
+  std::optional<char> PeekChar();
+
+  // Returns the next |count| bytes of the input stream, or nullopt if fewer
+  // than |count| bytes remain, and advances the parser position by |count|.
+  std::optional<std::string_view> ConsumeChars(int count);
+
+  // Calls ConsumeChars() with a |count| of 1.
+  std::optional<char> ConsumeChar();
+
+  // Returns a pointer to the current character position.
+  const char* pos();
+
+  // Skips over whitespace and comments to find the next token in the stream.
+  // This does not advance the parser for non-whitespace or comment chars.
+  Token GetNextToken();
+
+  // Consumes whitespace characters and comments until the next non-that is
+  // encountered.
+  void EatWhitespaceAndComments();
+  // Helper function that consumes a comment, assuming that the parser is
+  // currently wound to a '/'.
+  bool EatComment();
+
+  // Calls GetNextToken() and then ParseToken().
+  std::optional<Value> ParseNextToken();
+
+  // Takes a token that represents the start of a Value ("a structural token"
+  // in RFC terms) and consumes it, returning the result as a Value.
+  std::optional<Value> ParseToken(Token token);
+
+  // Assuming that the parser is currently wound to '{', this parses a JSON
+  // object into a Value.
+  std::optional<Value> ConsumeDictionary();
+
+  // Assuming that the parser is wound to '[', this parses a JSON list into a
+  // Value.
+  std::optional<Value> ConsumeList();
+
+  // Calls through ConsumeStringRaw and wraps it in a value.
+  std::optional<Value> ConsumeString();
+
+  // Assuming that the parser is wound to a double quote, this parses a string,
+  // decoding any escape sequences and converts UTF-16 to UTF-8. Returns true on
+  // success and places result into |out|. Returns false on failure with
+  // error information set.
+  bool ConsumeStringRaw(StringBuilder* out);
+  // Helper function for ConsumeStringRaw() that consumes the next four or 10
+  // bytes (parser is wound to the first character of a HEX sequence, with the
+  // potential for consuming another \uXXXX for a surrogate). Returns true on
+  // success and places the code point |out_code_point|, and false on failure.
+  bool DecodeUTF16(uint32_t* out_code_point);
+
+  // Assuming that the parser is wound to the start of a valid JSON number,
+  // this parses and converts it to either an int or double value.
+  std::optional<Value> ConsumeNumber();
+  // Helper that reads characters that are ints. Returns true if a number was
+  // read and false on error.
+  bool ReadInt(bool allow_leading_zeros);
+
+  // Consumes the literal values of |true|, |false|, and |null|, assuming the
+  // parser is wound to the first character of any of those.
+  std::optional<Value> ConsumeLiteral();
+
+  // Helper function that returns true if the byte sequence |match| can be
+  // consumed at the current parser position. Returns false if there are fewer
+  // than |match|-length bytes or if the sequence does not match, and the
+  // parser state is unchanged.
+  bool ConsumeIfMatch(std::string_view match);
+
+  // Sets the error information to |code| at the current column, based on
+  // |index_| and |index_last_line_|, with an optional positive/negative
+  // adjustment by |column_adjust|.
+  void ReportError(JSONReader::JsonParseError code, int column_adjust);
+
+  // Given the line and column number of an error, formats one of the error
+  // message contants from json_reader.h for human display.
+  static std::string FormatErrorMessage(int line,
+                                        int column,
+                                        const std::string& description);
+
+  // base::JSONParserOptions that control parsing.
+  const int options_;
+
+  // Maximum depth to parse.
+  const int max_depth_;
+
+  // The input stream being parsed. Note: Not guaranteed to NUL-terminated.
+  std::string_view input_;
+
+  // The index in the input stream to which the parser is wound.
+  int index_;
+
+  // The number of times the parser has recursed (current stack depth).
+  int stack_depth_;
+
+  // The line number that the parser is at currently.
+  int line_number_;
+
+  // The last value of |index_| on the previous line.
+  int index_last_line_;
+
+  // Error information.
+  JSONReader::JsonParseError error_code_;
+  int error_line_;
+  int error_column_;
+
+  friend class JSONParserTest;
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, NextChar);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeDictionary);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeList);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeString);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeLiterals);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeNumbers);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ErrorMessages);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ReplaceInvalidCharacters);
+  FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ReplaceInvalidUTF16EscapeSequence);
+
+  DISALLOW_COPY_AND_ASSIGN(JSONParser);
+};
+
+// Used when decoding and an invalid utf-8 sequence is encountered.
+extern const char kUnicodeReplacementString[];
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_JSON_JSON_PARSER_H_
diff --git a/src/base/json/json_reader.cc b/src/base/json/json_reader.cc
new file mode 100644 (file)
index 0000000..7b5b8eb
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/json/json_parser.h"
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace base {
+
+// Chosen to support 99.9% of documents found in the wild late 2016.
+// http://crbug.com/673263
+const int JSONReader::kStackMaxDepth = 200;
+
+// Values 1000 and above are used by JSONFileValueSerializer::JsonFileError.
+static_assert(JSONReader::JSON_PARSE_ERROR_COUNT < 1000,
+              "JSONReader error out of bounds");
+
+const char JSONReader::kInvalidEscape[] = "Invalid escape sequence.";
+const char JSONReader::kSyntaxError[] = "Syntax error.";
+const char JSONReader::kUnexpectedToken[] = "Unexpected token.";
+const char JSONReader::kTrailingComma[] = "Trailing comma not allowed.";
+const char JSONReader::kTooMuchNesting[] = "Too much nesting.";
+const char JSONReader::kUnexpectedDataAfterRoot[] =
+    "Unexpected data after root element.";
+const char JSONReader::kUnsupportedEncoding[] =
+    "Unsupported encoding. JSON must be UTF-8.";
+const char JSONReader::kUnquotedDictionaryKey[] =
+    "Dictionary keys must be quoted.";
+const char JSONReader::kInputTooLarge[] = "Input string is too large (>2GB).";
+
+JSONReader::JSONReader(int options, int max_depth)
+    : parser_(new internal::JSONParser(options, max_depth)) {}
+
+JSONReader::~JSONReader() = default;
+
+// static
+std::unique_ptr<Value> JSONReader::Read(std::string_view json,
+                                        int options,
+                                        int max_depth) {
+  internal::JSONParser parser(options, max_depth);
+  std::optional<Value> root = parser.Parse(json);
+  return root ? std::make_unique<Value>(std::move(*root)) : nullptr;
+}
+
+// static
+std::unique_ptr<Value> JSONReader::ReadAndReturnError(
+    std::string_view json,
+    int options,
+    int* error_code_out,
+    std::string* error_msg_out,
+    int* error_line_out,
+    int* error_column_out) {
+  internal::JSONParser parser(options);
+  std::optional<Value> root = parser.Parse(json);
+  if (!root) {
+    if (error_code_out)
+      *error_code_out = parser.error_code();
+    if (error_msg_out)
+      *error_msg_out = parser.GetErrorMessage();
+    if (error_line_out)
+      *error_line_out = parser.error_line();
+    if (error_column_out)
+      *error_column_out = parser.error_column();
+  }
+
+  return root ? std::make_unique<Value>(std::move(*root)) : nullptr;
+}
+
+// static
+std::string JSONReader::ErrorCodeToString(JsonParseError error_code) {
+  switch (error_code) {
+    case JSON_NO_ERROR:
+      return std::string();
+    case JSON_INVALID_ESCAPE:
+      return kInvalidEscape;
+    case JSON_SYNTAX_ERROR:
+      return kSyntaxError;
+    case JSON_UNEXPECTED_TOKEN:
+      return kUnexpectedToken;
+    case JSON_TRAILING_COMMA:
+      return kTrailingComma;
+    case JSON_TOO_MUCH_NESTING:
+      return kTooMuchNesting;
+    case JSON_UNEXPECTED_DATA_AFTER_ROOT:
+      return kUnexpectedDataAfterRoot;
+    case JSON_UNSUPPORTED_ENCODING:
+      return kUnsupportedEncoding;
+    case JSON_UNQUOTED_DICTIONARY_KEY:
+      return kUnquotedDictionaryKey;
+    case JSON_TOO_LARGE:
+      return kInputTooLarge;
+    case JSON_PARSE_ERROR_COUNT:
+      break;
+  }
+  NOTREACHED();
+  return std::string();
+}
+
+std::unique_ptr<Value> JSONReader::ReadToValue(std::string_view json) {
+  std::optional<Value> value = parser_->Parse(json);
+  return value ? std::make_unique<Value>(std::move(*value)) : nullptr;
+}
+
+JSONReader::JsonParseError JSONReader::error_code() const {
+  return parser_->error_code();
+}
+
+std::string JSONReader::GetErrorMessage() const {
+  return parser_->GetErrorMessage();
+}
+
+}  // namespace base
diff --git a/src/base/json/json_reader.h b/src/base/json/json_reader.h
new file mode 100644 (file)
index 0000000..c454981
--- /dev/null
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A JSON parser.  Converts strings of JSON into a Value object (see
+// base/values.h).
+// http://www.ietf.org/rfc/rfc4627.txt?number=4627
+//
+// Known limitations/deviations from the RFC:
+// - Only knows how to parse ints within the range of a signed 32 bit int and
+//   decimal numbers within a double.
+// - Assumes input is encoded as UTF8.  The spec says we should allow UTF-16
+//   (BE or LE) and UTF-32 (BE or LE) as well.
+// - We limit nesting to 100 levels to prevent stack overflow (this is allowed
+//   by the RFC).
+// - A Unicode FAQ ("http://unicode.org/faq/utf_bom.html") writes a data
+//   stream may start with a Unicode Byte-Order-Mark (U+FEFF), i.e. the input
+//   UTF-8 string for the JSONReader::JsonToValue() function may start with a
+//   UTF-8 BOM (0xEF, 0xBB, 0xBF).
+//   To avoid the function from mis-treating a UTF-8 BOM as an invalid
+//   character, the function skips a Unicode BOM at the beginning of the
+//   Unicode string (converted from the input UTF-8 string) before parsing it.
+//
+// TODO(tc): Add a parsing option to to relax object keys being wrapped in
+//   double quotes
+// TODO(tc): Add an option to disable comment stripping
+
+#ifndef BASE_JSON_JSON_READER_H_
+#define BASE_JSON_JSON_READER_H_
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace base {
+
+class Value;
+
+namespace internal {
+class JSONParser;
+}
+
+enum JSONParserOptions {
+  // Parses the input strictly according to RFC 4627, except for where noted
+  // above.
+  JSON_PARSE_RFC = 0,
+
+  // Allows commas to exist after the last element in structures.
+  JSON_ALLOW_TRAILING_COMMAS = 1 << 0,
+
+  // If set the parser replaces invalid characters with the Unicode replacement
+  // character (U+FFFD). If not set, invalid characters trigger a hard error and
+  // parsing fails.
+  JSON_REPLACE_INVALID_CHARACTERS = 1 << 1,
+};
+
+class JSONReader {
+ public:
+  static const int kStackMaxDepth;
+
+  // Error codes during parsing.
+  enum JsonParseError {
+    JSON_NO_ERROR = 0,
+    JSON_INVALID_ESCAPE,
+    JSON_SYNTAX_ERROR,
+    JSON_UNEXPECTED_TOKEN,
+    JSON_TRAILING_COMMA,
+    JSON_TOO_MUCH_NESTING,
+    JSON_UNEXPECTED_DATA_AFTER_ROOT,
+    JSON_UNSUPPORTED_ENCODING,
+    JSON_UNQUOTED_DICTIONARY_KEY,
+    JSON_TOO_LARGE,
+    JSON_PARSE_ERROR_COUNT
+  };
+
+  // String versions of parse error codes.
+  static const char kInvalidEscape[];
+  static const char kSyntaxError[];
+  static const char kUnexpectedToken[];
+  static const char kTrailingComma[];
+  static const char kTooMuchNesting[];
+  static const char kUnexpectedDataAfterRoot[];
+  static const char kUnsupportedEncoding[];
+  static const char kUnquotedDictionaryKey[];
+  static const char kInputTooLarge[];
+
+  // Constructs a reader.
+  JSONReader(int options = JSON_PARSE_RFC, int max_depth = kStackMaxDepth);
+
+  ~JSONReader();
+
+  // Reads and parses |json|, returning a Value.
+  // If |json| is not a properly formed JSON string, returns nullptr.
+  // Wrap this in base::FooValue::From() to check the Value is of type Foo and
+  // convert to a FooValue at the same time.
+  static std::unique_ptr<Value> Read(std::string_view json,
+                                     int options = JSON_PARSE_RFC,
+                                     int max_depth = kStackMaxDepth);
+
+  // Reads and parses |json| like Read(). |error_code_out| and |error_msg_out|
+  // are optional. If specified and nullptr is returned, they will be populated
+  // an error code and a formatted error message (including error location if
+  // appropriate). Otherwise, they will be unmodified.
+  static std::unique_ptr<Value> ReadAndReturnError(
+      std::string_view json,
+      int options,  // JSONParserOptions
+      int* error_code_out,
+      std::string* error_msg_out,
+      int* error_line_out = nullptr,
+      int* error_column_out = nullptr);
+
+  // Converts a JSON parse error code into a human readable message.
+  // Returns an empty string if error_code is JSON_NO_ERROR.
+  static std::string ErrorCodeToString(JsonParseError error_code);
+
+  // Non-static version of Read() above.
+  std::unique_ptr<Value> ReadToValue(std::string_view json);
+
+  // Returns the error code if the last call to ReadToValue() failed.
+  // Returns JSON_NO_ERROR otherwise.
+  JsonParseError error_code() const;
+
+  // Converts error_code_ to a human-readable string, including line and column
+  // numbers if appropriate.
+  std::string GetErrorMessage() const;
+
+ private:
+  std::unique_ptr<internal::JSONParser> parser_;
+};
+
+}  // namespace base
+
+#endif  // BASE_JSON_JSON_READER_H_
diff --git a/src/base/json/json_value_converter.cc b/src/base/json/json_value_converter.cc
new file mode 100644 (file)
index 0000000..ee7b094
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_value_converter.h"
+
+namespace base {
+namespace internal {
+
+bool BasicValueConverter<int>::Convert(const base::Value& value,
+                                       int* field) const {
+  return value.GetAsInteger(field);
+}
+
+bool BasicValueConverter<std::string>::Convert(const base::Value& value,
+                                               std::string* field) const {
+  return value.GetAsString(field);
+}
+
+bool BasicValueConverter<std::u16string>::Convert(const base::Value& value,
+                                                  std::u16string* field) const {
+  return value.GetAsString(field);
+}
+
+bool BasicValueConverter<double>::Convert(const base::Value& value,
+                                          double* field) const {
+  return value.GetAsDouble(field);
+}
+
+bool BasicValueConverter<bool>::Convert(const base::Value& value,
+                                        bool* field) const {
+  return value.GetAsBoolean(field);
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/src/base/json/json_value_converter.h b/src/base/json/json_value_converter.h
new file mode 100644 (file)
index 0000000..f3030f2
--- /dev/null
@@ -0,0 +1,515 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_JSON_JSON_VALUE_CONVERTER_H_
+#define BASE_JSON_JSON_VALUE_CONVERTER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+
+// JSONValueConverter converts a JSON value into a C++ struct in a
+// lightweight way.
+//
+// Usage:
+// For real examples, you may want to refer to _unittest.cc file.
+//
+// Assume that you have a struct like this:
+//   struct Message {
+//     int foo;
+//     std::string bar;
+//     static void RegisterJSONConverter(
+//         JSONValueConverter<Message>* converter);
+//   };
+//
+// And you want to parse a json data into this struct.  First, you
+// need to declare RegisterJSONConverter() method in your struct.
+//   // static
+//   void Message::RegisterJSONConverter(
+//       JSONValueConverter<Message>* converter) {
+//     converter->RegisterIntField("foo", &Message::foo);
+//     converter->RegisterStringField("bar", &Message::bar);
+//   }
+//
+// Then, you just instantiate your JSONValueConverter of your type and call
+// Convert() method.
+//   Message message;
+//   JSONValueConverter<Message> converter;
+//   converter.Convert(json, &message);
+//
+// Convert() returns false when it fails.  Here "fail" means that the value is
+// structurally different from expected, such like a string value appears
+// for an int field.  Do not report failures for missing fields.
+// Also note that Convert() will modify the passed |message| even when it
+// fails for performance reason.
+//
+// For nested field, the internal message also has to implement the registration
+// method.  Then, just use RegisterNestedField() from the containing struct's
+// RegisterJSONConverter method.
+//   struct Nested {
+//     Message foo;
+//     static void RegisterJSONConverter(...) {
+//       ...
+//       converter->RegisterNestedField("foo", &Nested::foo);
+//     }
+//   };
+//
+// For repeated field, we just assume std::vector<std::unique_ptr<ElementType>>
+// for its container and you can put RegisterRepeatedInt or some other types.
+// Use RegisterRepeatedMessage for nested repeated fields.
+//
+// Sometimes JSON format uses string representations for other types such
+// like enum, timestamp, or URL.  You can use RegisterCustomField method
+// and specify a function to convert a std::string_view to your type.
+//   bool ConvertFunc(std::string_view s, YourEnum* result) {
+//     // do something and return true if succeed...
+//   }
+//   struct Message {
+//     YourEnum ye;
+//     ...
+//     static void RegisterJSONConverter(...) {
+//       ...
+//       converter->RegsiterCustomField<YourEnum>(
+//           "your_enum", &Message::ye, &ConvertFunc);
+//     }
+//   };
+
+namespace base {
+
+template <typename StructType>
+class JSONValueConverter;
+
+namespace internal {
+
+template <typename StructType>
+class FieldConverterBase {
+ public:
+  explicit FieldConverterBase(const std::string& path) : field_path_(path) {}
+  virtual ~FieldConverterBase() = default;
+  virtual bool ConvertField(const base::Value& value,
+                            StructType* obj) const = 0;
+  const std::string& field_path() const { return field_path_; }
+
+ private:
+  std::string field_path_;
+  DISALLOW_COPY_AND_ASSIGN(FieldConverterBase);
+};
+
+template <typename FieldType>
+class ValueConverter {
+ public:
+  virtual ~ValueConverter() = default;
+  virtual bool Convert(const base::Value& value, FieldType* field) const = 0;
+};
+
+template <typename StructType, typename FieldType>
+class FieldConverter : public FieldConverterBase<StructType> {
+ public:
+  explicit FieldConverter(const std::string& path,
+                          FieldType StructType::*field,
+                          ValueConverter<FieldType>* converter)
+      : FieldConverterBase<StructType>(path),
+        field_pointer_(field),
+        value_converter_(converter) {}
+
+  bool ConvertField(const base::Value& value, StructType* dst) const override {
+    return value_converter_->Convert(value, &(dst->*field_pointer_));
+  }
+
+ private:
+  FieldType StructType::*field_pointer_;
+  std::unique_ptr<ValueConverter<FieldType>> value_converter_;
+  DISALLOW_COPY_AND_ASSIGN(FieldConverter);
+};
+
+template <typename FieldType>
+class BasicValueConverter;
+
+template <>
+class BasicValueConverter<int> : public ValueConverter<int> {
+ public:
+  BasicValueConverter() = default;
+
+  bool Convert(const base::Value& value, int* field) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BasicValueConverter);
+};
+
+template <>
+class BasicValueConverter<std::string> : public ValueConverter<std::string> {
+ public:
+  BasicValueConverter() = default;
+
+  bool Convert(const base::Value& value, std::string* field) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BasicValueConverter);
+};
+
+template <>
+class BasicValueConverter<std::u16string>
+    : public ValueConverter<std::u16string> {
+ public:
+  BasicValueConverter() = default;
+
+  bool Convert(const base::Value& value, std::u16string* field) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BasicValueConverter);
+};
+
+template <>
+class BasicValueConverter<double> : public ValueConverter<double> {
+ public:
+  BasicValueConverter() = default;
+
+  bool Convert(const base::Value& value, double* field) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BasicValueConverter);
+};
+
+template <>
+class BasicValueConverter<bool> : public ValueConverter<bool> {
+ public:
+  BasicValueConverter() = default;
+
+  bool Convert(const base::Value& value, bool* field) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BasicValueConverter);
+};
+
+template <typename FieldType>
+class ValueFieldConverter : public ValueConverter<FieldType> {
+ public:
+  typedef bool (*ConvertFunc)(const base::Value* value, FieldType* field);
+
+  explicit ValueFieldConverter(ConvertFunc convert_func)
+      : convert_func_(convert_func) {}
+
+  bool Convert(const base::Value& value, FieldType* field) const override {
+    return convert_func_(&value, field);
+  }
+
+ private:
+  ConvertFunc convert_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(ValueFieldConverter);
+};
+
+template <typename FieldType>
+class CustomFieldConverter : public ValueConverter<FieldType> {
+ public:
+  typedef bool (*ConvertFunc)(std::string_view value, FieldType* field);
+
+  explicit CustomFieldConverter(ConvertFunc convert_func)
+      : convert_func_(convert_func) {}
+
+  bool Convert(const base::Value& value, FieldType* field) const override {
+    std::string string_value;
+    return value.GetAsString(&string_value) &&
+           convert_func_(string_value, field);
+  }
+
+ private:
+  ConvertFunc convert_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(CustomFieldConverter);
+};
+
+template <typename NestedType>
+class NestedValueConverter : public ValueConverter<NestedType> {
+ public:
+  NestedValueConverter() = default;
+
+  bool Convert(const base::Value& value, NestedType* field) const override {
+    return converter_.Convert(value, field);
+  }
+
+ private:
+  JSONValueConverter<NestedType> converter_;
+  DISALLOW_COPY_AND_ASSIGN(NestedValueConverter);
+};
+
+template <typename Element>
+class RepeatedValueConverter
+    : public ValueConverter<std::vector<std::unique_ptr<Element>>> {
+ public:
+  RepeatedValueConverter() = default;
+
+  bool Convert(const base::Value& value,
+               std::vector<std::unique_ptr<Element>>* field) const override {
+    const base::ListValue* list = NULL;
+    if (!value.GetAsList(&list)) {
+      // The field is not a list.
+      return false;
+    }
+
+    field->reserve(list->GetSize());
+    for (size_t i = 0; i < list->GetSize(); ++i) {
+      const base::Value* element = NULL;
+      if (!list->Get(i, &element))
+        continue;
+
+      std::unique_ptr<Element> e(new Element);
+      if (basic_converter_.Convert(*element, e.get())) {
+        field->push_back(std::move(e));
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  BasicValueConverter<Element> basic_converter_;
+  DISALLOW_COPY_AND_ASSIGN(RepeatedValueConverter);
+};
+
+template <typename NestedType>
+class RepeatedMessageConverter
+    : public ValueConverter<std::vector<std::unique_ptr<NestedType>>> {
+ public:
+  RepeatedMessageConverter() = default;
+
+  bool Convert(const base::Value& value,
+               std::vector<std::unique_ptr<NestedType>>* field) const override {
+    const base::ListValue* list = NULL;
+    if (!value.GetAsList(&list))
+      return false;
+
+    field->reserve(list->GetSize());
+    for (size_t i = 0; i < list->GetSize(); ++i) {
+      const base::Value* element = NULL;
+      if (!list->Get(i, &element))
+        continue;
+
+      std::unique_ptr<NestedType> nested(new NestedType);
+      if (converter_.Convert(*element, nested.get())) {
+        field->push_back(std::move(nested));
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  JSONValueConverter<NestedType> converter_;
+  DISALLOW_COPY_AND_ASSIGN(RepeatedMessageConverter);
+};
+
+template <typename NestedType>
+class RepeatedCustomValueConverter
+    : public ValueConverter<std::vector<std::unique_ptr<NestedType>>> {
+ public:
+  typedef bool (*ConvertFunc)(const base::Value* value, NestedType* field);
+
+  explicit RepeatedCustomValueConverter(ConvertFunc convert_func)
+      : convert_func_(convert_func) {}
+
+  bool Convert(const base::Value& value,
+               std::vector<std::unique_ptr<NestedType>>* field) const override {
+    const base::ListValue* list = NULL;
+    if (!value.GetAsList(&list))
+      return false;
+
+    field->reserve(list->GetSize());
+    for (size_t i = 0; i < list->GetSize(); ++i) {
+      const base::Value* element = NULL;
+      if (!list->Get(i, &element))
+        continue;
+
+      std::unique_ptr<NestedType> nested(new NestedType);
+      if ((*convert_func_)(element, nested.get())) {
+        field->push_back(std::move(nested));
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  ConvertFunc convert_func_;
+  DISALLOW_COPY_AND_ASSIGN(RepeatedCustomValueConverter);
+};
+
+}  // namespace internal
+
+template <class StructType>
+class JSONValueConverter {
+ public:
+  JSONValueConverter() { StructType::RegisterJSONConverter(this); }
+
+  void RegisterIntField(const std::string& field_name, int StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, int>>(
+            field_name, field, new internal::BasicValueConverter<int>));
+  }
+
+  void RegisterStringField(const std::string& field_name,
+                           std::string StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, std::string>>(
+            field_name, field, new internal::BasicValueConverter<std::string>));
+  }
+
+  void RegisterStringField(const std::string& field_name,
+                           std::u16string StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, std::u16string>>(
+            field_name, field,
+            new internal::BasicValueConverter<std::u16string>));
+  }
+
+  void RegisterBoolField(const std::string& field_name,
+                         bool StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, bool>>(
+            field_name, field, new internal::BasicValueConverter<bool>));
+  }
+
+  void RegisterDoubleField(const std::string& field_name,
+                           double StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, double>>(
+            field_name, field, new internal::BasicValueConverter<double>));
+  }
+
+  template <class NestedType>
+  void RegisterNestedField(const std::string& field_name,
+                           NestedType StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, NestedType>>(
+            field_name, field, new internal::NestedValueConverter<NestedType>));
+  }
+
+  template <typename FieldType>
+  void RegisterCustomField(const std::string& field_name,
+                           FieldType StructType::*field,
+                           bool (*convert_func)(std::string_view, FieldType*)) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, FieldType>>(
+            field_name, field,
+            new internal::CustomFieldConverter<FieldType>(convert_func)));
+  }
+
+  template <typename FieldType>
+  void RegisterCustomValueField(const std::string& field_name,
+                                FieldType StructType::*field,
+                                bool (*convert_func)(const base::Value*,
+                                                     FieldType*)) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<StructType, FieldType>>(
+            field_name, field,
+            new internal::ValueFieldConverter<FieldType>(convert_func)));
+  }
+
+  void RegisterRepeatedInt(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<int>> StructType::*field) {
+    fields_.push_back(std::make_unique<internal::FieldConverter<
+                          StructType, std::vector<std::unique_ptr<int>>>>(
+        field_name, field, new internal::RepeatedValueConverter<int>));
+  }
+
+  void RegisterRepeatedString(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<std::string>> StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<
+            StructType, std::vector<std::unique_ptr<std::string>>>>(
+            field_name, field,
+            new internal::RepeatedValueConverter<std::string>));
+  }
+
+  void RegisterRepeatedString(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<std::u16string>> StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<
+            StructType, std::vector<std::unique_ptr<std::u16string>>>>(
+            field_name, field,
+            new internal::RepeatedValueConverter<std::u16string>));
+  }
+
+  void RegisterRepeatedDouble(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<double>> StructType::*field) {
+    fields_.push_back(std::make_unique<internal::FieldConverter<
+                          StructType, std::vector<std::unique_ptr<double>>>>(
+        field_name, field, new internal::RepeatedValueConverter<double>));
+  }
+
+  void RegisterRepeatedBool(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<bool>> StructType::*field) {
+    fields_.push_back(std::make_unique<internal::FieldConverter<
+                          StructType, std::vector<std::unique_ptr<bool>>>>(
+        field_name, field, new internal::RepeatedValueConverter<bool>));
+  }
+
+  template <class NestedType>
+  void RegisterRepeatedCustomValue(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<NestedType>> StructType::*field,
+      bool (*convert_func)(const base::Value*, NestedType*)) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<
+            StructType, std::vector<std::unique_ptr<NestedType>>>>(
+            field_name, field,
+            new internal::RepeatedCustomValueConverter<NestedType>(
+                convert_func)));
+  }
+
+  template <class NestedType>
+  void RegisterRepeatedMessage(
+      const std::string& field_name,
+      std::vector<std::unique_ptr<NestedType>> StructType::*field) {
+    fields_.push_back(
+        std::make_unique<internal::FieldConverter<
+            StructType, std::vector<std::unique_ptr<NestedType>>>>(
+            field_name, field,
+            new internal::RepeatedMessageConverter<NestedType>));
+  }
+
+  bool Convert(const base::Value& value, StructType* output) const {
+    const DictionaryValue* dictionary_value = NULL;
+    if (!value.GetAsDictionary(&dictionary_value))
+      return false;
+
+    for (size_t i = 0; i < fields_.size(); ++i) {
+      const internal::FieldConverterBase<StructType>* field_converter =
+          fields_[i].get();
+      const base::Value* field = NULL;
+      if (dictionary_value->Get(field_converter->field_path(), &field)) {
+        if (!field_converter->ConvertField(*field, output)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+ private:
+  std::vector<std::unique_ptr<internal::FieldConverterBase<StructType>>>
+      fields_;
+
+  DISALLOW_COPY_AND_ASSIGN(JSONValueConverter);
+};
+
+}  // namespace base
+
+#endif  // BASE_JSON_JSON_VALUE_CONVERTER_H_
diff --git a/src/base/json/json_writer.cc b/src/base/json/json_writer.cc
new file mode 100644 (file)
index 0000000..912a7f0
--- /dev/null
@@ -0,0 +1,175 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_writer.h"
+
+#include <stdint.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "util/build_config.h"
+
+namespace base {
+
+#if defined(OS_WIN)
+const char kPrettyPrintLineEnding[] = "\r\n";
+#else
+const char kPrettyPrintLineEnding[] = "\n";
+#endif
+
+// static
+bool JSONWriter::Write(const Value& node, std::string* json) {
+  return WriteWithOptions(node, 0, json);
+}
+
+// static
+bool JSONWriter::WriteWithOptions(const Value& node,
+                                  int options,
+                                  std::string* json) {
+  json->clear();
+  // Is there a better way to estimate the size of the output?
+  json->reserve(1024);
+
+  JSONWriter writer(options, json);
+  bool result = writer.BuildJSONString(node, 0U);
+
+  if (options & OPTIONS_PRETTY_PRINT)
+    json->append(kPrettyPrintLineEnding);
+
+  return result;
+}
+
+JSONWriter::JSONWriter(int options, std::string* json)
+    : omit_binary_values_((options & OPTIONS_OMIT_BINARY_VALUES) != 0),
+      pretty_print_((options & OPTIONS_PRETTY_PRINT) != 0),
+      json_string_(json) {
+  DCHECK(json);
+}
+
+bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
+  switch (node.type()) {
+    case Value::Type::NONE: {
+      json_string_->append("null");
+      return true;
+    }
+
+    case Value::Type::BOOLEAN: {
+      bool value;
+      bool result = node.GetAsBoolean(&value);
+      DCHECK(result);
+      json_string_->append(value ? "true" : "false");
+      return result;
+    }
+
+    case Value::Type::INTEGER: {
+      int value;
+      bool result = node.GetAsInteger(&value);
+      DCHECK(result);
+      json_string_->append(IntToString(value));
+      return result;
+    }
+
+    case Value::Type::STRING: {
+      std::string value;
+      bool result = node.GetAsString(&value);
+      DCHECK(result);
+      EscapeJSONString(value, true, json_string_);
+      return result;
+    }
+
+    case Value::Type::LIST: {
+      json_string_->push_back('[');
+      if (pretty_print_)
+        json_string_->push_back(' ');
+
+      const ListValue* list = nullptr;
+      bool first_value_has_been_output = false;
+      bool result = node.GetAsList(&list);
+      DCHECK(result);
+      for (const auto& value : *list) {
+        if (omit_binary_values_ && value.type() == Value::Type::BINARY)
+          continue;
+
+        if (first_value_has_been_output) {
+          json_string_->push_back(',');
+          if (pretty_print_)
+            json_string_->push_back(' ');
+        }
+
+        if (!BuildJSONString(value, depth))
+          result = false;
+
+        first_value_has_been_output = true;
+      }
+
+      if (pretty_print_)
+        json_string_->push_back(' ');
+      json_string_->push_back(']');
+      return result;
+    }
+
+    case Value::Type::DICTIONARY: {
+      json_string_->push_back('{');
+      if (pretty_print_)
+        json_string_->append(kPrettyPrintLineEnding);
+
+      const DictionaryValue* dict = nullptr;
+      bool first_value_has_been_output = false;
+      bool result = node.GetAsDictionary(&dict);
+      DCHECK(result);
+      for (DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd();
+           itr.Advance()) {
+        if (omit_binary_values_ && itr.value().type() == Value::Type::BINARY) {
+          continue;
+        }
+
+        if (first_value_has_been_output) {
+          json_string_->push_back(',');
+          if (pretty_print_)
+            json_string_->append(kPrettyPrintLineEnding);
+        }
+
+        if (pretty_print_)
+          IndentLine(depth + 1U);
+
+        EscapeJSONString(itr.key(), true, json_string_);
+        json_string_->push_back(':');
+        if (pretty_print_)
+          json_string_->push_back(' ');
+
+        if (!BuildJSONString(itr.value(), depth + 1U))
+          result = false;
+
+        first_value_has_been_output = true;
+      }
+
+      if (pretty_print_) {
+        json_string_->append(kPrettyPrintLineEnding);
+        IndentLine(depth);
+      }
+
+      json_string_->push_back('}');
+      return result;
+    }
+
+    case Value::Type::BINARY:
+      // Successful only if we're allowed to omit it.
+      DLOG_IF(ERROR, !omit_binary_values_) << "Cannot serialize binary value.";
+      return omit_binary_values_;
+  }
+  NOTREACHED();
+  return false;
+}
+
+void JSONWriter::IndentLine(size_t depth) {
+  json_string_->append(depth * 3U, ' ');
+}
+
+}  // namespace base
diff --git a/src/base/json/json_writer.h b/src/base/json/json_writer.h
new file mode 100644 (file)
index 0000000..4114f77
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_JSON_JSON_WRITER_H_
+#define BASE_JSON_JSON_WRITER_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace base {
+
+class Value;
+
+class JSONWriter {
+ public:
+  enum Options {
+    // This option instructs the writer that if a Binary value is encountered,
+    // the value (and key if within a dictionary) will be omitted from the
+    // output, and success will be returned. Otherwise, if a binary value is
+    // encountered, failure will be returned.
+    OPTIONS_OMIT_BINARY_VALUES = 1 << 0,
+
+    // Return a slightly nicer formatted json string (pads with whitespace to
+    // help with readability).
+    OPTIONS_PRETTY_PRINT = 1 << 2,
+  };
+
+  // Given a root node, generates a JSON string and puts it into |json|.
+  // The output string is overwritten and not appended.
+  //
+  // TODO(tc): Should we generate json if it would be invalid json (e.g.,
+  // |node| is not a DictionaryValue/ListValue or if there are inf/-inf float
+  // values)? Return true on success and false on failure.
+  static bool Write(const Value& node, std::string* json);
+
+  // Same as above but with |options| which is a bunch of JSONWriter::Options
+  // bitwise ORed together. Return true on success and false on failure.
+  static bool WriteWithOptions(const Value& node,
+                               int options,
+                               std::string* json);
+
+ private:
+  JSONWriter(int options, std::string* json);
+
+  // Called recursively to build the JSON string. When completed,
+  // |json_string_| will contain the JSON.
+  bool BuildJSONString(const Value& node, size_t depth);
+
+  // Adds space to json_string_ for the indent level.
+  void IndentLine(size_t depth);
+
+  bool omit_binary_values_;
+  bool pretty_print_;
+
+  // Where we write JSON data as we generate it.
+  std::string* json_string_;
+
+  DISALLOW_COPY_AND_ASSIGN(JSONWriter);
+};
+
+}  // namespace base
+
+#endif  // BASE_JSON_JSON_WRITER_H_
diff --git a/src/base/json/string_escape.cc b/src/base/json/string_escape.cc
new file mode 100644 (file)
index 0000000..51a818c
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/string_escape.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/third_party/icu/icu_utf.h"
+
+namespace base {
+
+namespace {
+
+// Format string for printing a \uXXXX escape sequence.
+const char kU16EscapeFormat[] = "\\u%04X";
+
+// The code point to output for an invalid input code unit.
+const uint32_t kReplacementCodePoint = 0xFFFD;
+
+// Used below in EscapeSpecialCodePoint().
+static_assert('<' == 0x3C, "less than sign must be 0x3c");
+
+// Try to escape the |code_point| if it is a known special character. If
+// successful, returns true and appends the escape sequence to |dest|. This
+// isn't required by the spec, but it's more readable by humans.
+bool EscapeSpecialCodePoint(uint32_t code_point, std::string* dest) {
+  // WARNING: if you add a new case here, you need to update the reader as well.
+  // Note: \v is in the reader, but not here since the JSON spec doesn't
+  // allow it.
+  switch (code_point) {
+    case '\b':
+      dest->append("\\b");
+      break;
+    case '\f':
+      dest->append("\\f");
+      break;
+    case '\n':
+      dest->append("\\n");
+      break;
+    case '\r':
+      dest->append("\\r");
+      break;
+    case '\t':
+      dest->append("\\t");
+      break;
+    case '\\':
+      dest->append("\\\\");
+      break;
+    case '"':
+      dest->append("\\\"");
+      break;
+    // Escape < to prevent script execution; escaping > is not necessary and
+    // not doing so save a few bytes.
+    case '<':
+      dest->append("\\u003C");
+      break;
+    // Escape the "Line Separator" and "Paragraph Separator" characters, since
+    // they should be treated like a new line \r or \n.
+    case 0x2028:
+      dest->append("\\u2028");
+      break;
+    case 0x2029:
+      dest->append("\\u2029");
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+template <typename S>
+bool EscapeJSONStringImpl(const S& str, bool put_in_quotes, std::string* dest) {
+  bool did_replacement = false;
+
+  if (put_in_quotes)
+    dest->push_back('"');
+
+  // Casting is necessary because ICU uses int32_t. Try and do so safely.
+  CHECK_LE(str.length(),
+           static_cast<size_t>(std::numeric_limits<int32_t>::max()));
+  const int32_t length = static_cast<int32_t>(str.length());
+
+  for (int32_t i = 0; i < length; ++i) {
+    uint32_t code_point;
+    if (!ReadUnicodeCharacter(str.data(), length, &i, &code_point) ||
+        code_point == static_cast<decltype(code_point)>(CBU_SENTINEL) ||
+        !IsValidCharacter(code_point)) {
+      code_point = kReplacementCodePoint;
+      did_replacement = true;
+    }
+
+    if (EscapeSpecialCodePoint(code_point, dest))
+      continue;
+
+    // Escape non-printing characters.
+    if (code_point < 32)
+      base::StringAppendF(dest, kU16EscapeFormat, code_point);
+    else
+      WriteUnicodeCharacter(code_point, dest);
+  }
+
+  if (put_in_quotes)
+    dest->push_back('"');
+
+  return !did_replacement;
+}
+
+}  // namespace
+
+void EscapeJSONString(std::string_view str,
+                      bool put_in_quotes,
+                      std::string* dest) {
+  EscapeJSONStringImpl(str, put_in_quotes, dest);
+}
+
+void EscapeJSONString(std::u16string_view str,
+                      bool put_in_quotes,
+                      std::string* dest) {
+  EscapeJSONStringImpl(str, put_in_quotes, dest);
+}
+
+std::string EscapeBytesAsInvalidJSONString(std::string_view str,
+                                           bool put_in_quotes) {
+  std::string dest;
+
+  if (put_in_quotes)
+    dest.push_back('"');
+
+  for (std::string_view::const_iterator it = str.begin(); it != str.end();
+       ++it) {
+    unsigned char c = *it;
+    if (EscapeSpecialCodePoint(c, &dest))
+      continue;
+
+    if (c < 32 || c > 126)
+      base::StringAppendF(&dest, kU16EscapeFormat, c);
+    else
+      dest.push_back(*it);
+  }
+
+  if (put_in_quotes)
+    dest.push_back('"');
+
+  return dest;
+}
+
+}  // namespace base
diff --git a/src/base/json/string_escape.h b/src/base/json/string_escape.h
new file mode 100644 (file)
index 0000000..14eecdd
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines utility functions for escaping strings suitable for JSON.
+
+#ifndef BASE_JSON_STRING_ESCAPE_H_
+#define BASE_JSON_STRING_ESCAPE_H_
+
+#include <string>
+#include <string_view>
+
+namespace base {
+
+// Appends to |dest| an escaped version of |str|. Valid UTF-8 code units and
+// characters will pass through from the input to the output. Invalid code
+// units and characters will be replaced with the U+FFFD replacement character.
+// This function returns true if no replacement was necessary and false if
+// there was a lossy replacement. On return, |dest| will contain a valid UTF-8
+// JSON string.
+//
+// Non-printing control characters will be escaped as \uXXXX sequences for
+// readability.
+//
+// If |put_in_quotes| is true, then a leading and trailing double-quote mark
+// will be appended to |dest| as well.
+void EscapeJSONString(std::string_view str,
+                      bool put_in_quotes,
+                      std::string* dest);
+
+// Performs a similar function to the UTF-8 std::string_view version above,
+// converting UTF-16 code units to UTF-8 code units and escaping non-printing
+// control characters. On return, |dest| will contain a valid UTF-8 JSON string.
+void EscapeJSONString(std::u16string_view str,
+                      bool put_in_quotes,
+                      std::string* dest);
+
+// Given an arbitrary byte string |str|, this will escape all non-ASCII bytes
+// as \uXXXX escape sequences. This function is *NOT* meant to be used with
+// Unicode strings and does not validate |str| as one.
+//
+// CAVEAT CALLER: The output of this function may not be valid JSON, since
+// JSON requires escape sequences to be valid UTF-16 code units. This output
+// will be mangled if passed to to the base::JSONReader, since the reader will
+// interpret it as UTF-16 and convert it to UTF-8.
+//
+// The output of this function takes the *appearance* of JSON but is not in
+// fact valid according to RFC 4627.
+std::string EscapeBytesAsInvalidJSONString(std::string_view str,
+                                           bool put_in_quotes);
+
+}  // namespace base
+
+#endif  // BASE_JSON_STRING_ESCAPE_H_
diff --git a/src/base/logging.cc b/src/base/logging.cc
new file mode 100644 (file)
index 0000000..ee60c47
--- /dev/null
@@ -0,0 +1,325 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+
+#include <limits.h>
+#include <stdint.h>
+
+#include <iterator>
+#include <thread>
+
+#include "base/macros.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <io.h>
+#include <windows.h>
+// Windows warns on using write().  It prefers _write().
+#define write(fd, buf, count) _write(fd, buf, static_cast<unsigned int>(count))
+// Windows doesn't define STDERR_FILENO.  Define it here.
+#define STDERR_FILENO 2
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/time.h>
+#include <time.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
+#include <paths.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <cstring>
+#include <ctime>
+#include <iomanip>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/posix/safe_strerror.h"
+#endif
+
+namespace logging {
+
+namespace {
+
+const char* const log_severity_names[] = {"INFO", "WARNING", "ERROR", "FATAL"};
+static_assert(LOG_NUM_SEVERITIES == std::size(log_severity_names),
+              "Incorrect number of log_severity_names");
+
+const char* log_severity_name(int severity) {
+  if (severity >= 0 && severity < LOG_NUM_SEVERITIES)
+    return log_severity_names[severity];
+  return "UNKNOWN";
+}
+
+int g_min_log_level = 0;
+
+// For LOG_ERROR and above, always print to stderr.
+const int kAlwaysPrintErrorLevel = LOG_ERROR;
+
+}  // namespace
+
+#if DCHECK_IS_CONFIGURABLE
+// In DCHECK-enabled Chrome builds, allow the meaning of LOG_DCHECK to be
+// determined at run-time. We default it to INFO, to avoid it triggering
+// crashes before the run-time has explicitly chosen the behaviour.
+logging::LogSeverity LOG_DCHECK = LOG_INFO;
+#endif  // DCHECK_IS_CONFIGURABLE
+
+// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
+// an object of the correct type on the LHS of the unused part of the ternary
+// operator.
+std::ostream* g_swallow_stream;
+
+void SetMinLogLevel(int level) {
+  g_min_log_level = std::min(LOG_FATAL, level);
+}
+
+int GetMinLogLevel() {
+  return g_min_log_level;
+}
+
+bool ShouldCreateLogMessage(int severity) {
+  if (severity < g_min_log_level)
+    return false;
+
+  // Return true here unless we know ~LogMessage won't do anything. Note that
+  // ~LogMessage writes to stderr if severity_ >= kAlwaysPrintErrorLevel, even
+  // when g_logging_destination is LOG_NONE.
+  return severity >= kAlwaysPrintErrorLevel;
+}
+
+// Explicit instantiations for commonly used comparisons.
+template std::string* MakeCheckOpString<int, int>(const int&,
+                                                  const int&,
+                                                  const char* names);
+template std::string* MakeCheckOpString<unsigned long, unsigned long>(
+    const unsigned long&,
+    const unsigned long&,
+    const char* names);
+template std::string* MakeCheckOpString<unsigned long, unsigned int>(
+    const unsigned long&,
+    const unsigned int&,
+    const char* names);
+template std::string* MakeCheckOpString<unsigned int, unsigned long>(
+    const unsigned int&,
+    const unsigned long&,
+    const char* names);
+template std::string* MakeCheckOpString<std::string, std::string>(
+    const std::string&,
+    const std::string&,
+    const char* name);
+
+void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p) {
+  (*os) << "nullptr";
+}
+
+#if defined(OS_WIN)
+LogMessage::SaveLastError::SaveLastError() : last_error_(::GetLastError()) {}
+
+LogMessage::SaveLastError::~SaveLastError() {
+  ::SetLastError(last_error_);
+}
+#endif  // defined(OS_WIN)
+
+LogMessage::LogMessage(const char* file, int line, LogSeverity severity)
+    : severity_(severity) {
+  Init(file, line);
+}
+
+LogMessage::LogMessage(const char* file, int line, const char* condition)
+    : severity_(LOG_FATAL) {
+  Init(file, line);
+  stream_ << "Check failed: " << condition << ". ";
+}
+
+LogMessage::LogMessage(const char* file, int line, std::string* result)
+    : severity_(LOG_FATAL) {
+  Init(file, line);
+  stream_ << "Check failed: " << *result;
+  delete result;
+}
+
+LogMessage::LogMessage(const char* file,
+                       int line,
+                       LogSeverity severity,
+                       std::string* result)
+    : severity_(severity) {
+  Init(file, line);
+  stream_ << "Check failed: " << *result;
+  delete result;
+}
+
+LogMessage::~LogMessage() {
+  if (severity_ == LOG_FATAL) {
+    stream_ << std::endl;  // Newline to separate from log message.
+  }
+  stream_ << std::endl;
+  std::string str_newline(stream_.str());
+
+#if defined(OS_WIN)
+  OutputDebugStringA(str_newline.c_str());
+#endif
+  ignore_result(fwrite(str_newline.data(), str_newline.size(), 1, stderr));
+  fflush(stderr);
+
+  if (severity_ == LOG_FATAL) {
+    abort();
+  }
+}
+
+// writes the common header info to the stream
+void LogMessage::Init(const char* file, int line) {
+  std::string_view filename(file);
+  size_t last_slash_pos = filename.find_last_of("\\/");
+  if (last_slash_pos != std::string_view::npos)
+    filename.remove_prefix(last_slash_pos + 1);
+
+  // TODO(darin): It might be nice if the columns were fixed width.
+
+  stream_ << '[';
+  stream_ << std::this_thread::get_id() << ':';
+#if defined(OS_WIN)
+  SYSTEMTIME local_time;
+  GetLocalTime(&local_time);
+  stream_ << std::setfill('0') << std::setw(2) << local_time.wMonth
+          << std::setw(2) << local_time.wDay << '/' << std::setw(2)
+          << local_time.wHour << std::setw(2) << local_time.wMinute
+          << std::setw(2) << local_time.wSecond << '.' << std::setw(3)
+          << local_time.wMilliseconds << ':';
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  timeval tv;
+  gettimeofday(&tv, nullptr);
+  time_t t = tv.tv_sec;
+  struct tm local_time;
+  localtime_r(&t, &local_time);
+  struct tm* tm_time = &local_time;
+  stream_ << std::setfill('0') << std::setw(2) << 1 + tm_time->tm_mon
+          << std::setw(2) << tm_time->tm_mday << '/' << std::setw(2)
+          << tm_time->tm_hour << std::setw(2) << tm_time->tm_min << std::setw(2)
+          << tm_time->tm_sec << '.' << std::setw(6) << tv.tv_usec << ':';
+#else
+#error Unsupported platform
+#endif
+  if (severity_ >= 0)
+    stream_ << log_severity_name(severity_);
+  else
+    stream_ << "VERBOSE" << -severity_;
+
+  stream_ << ":" << filename << "(" << line << ")] ";
+
+  message_start_ = stream_.str().length();
+}
+
+#if defined(OS_WIN)
+// This has already been defined in the header, but defining it again as DWORD
+// ensures that the type used in the header is equivalent to DWORD. If not,
+// the redefinition is a compile error.
+typedef DWORD SystemErrorCode;
+#endif
+
+SystemErrorCode GetLastSystemErrorCode() {
+#if defined(OS_WIN)
+  return ::GetLastError();
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  return errno;
+#endif
+}
+
+std::string SystemErrorCodeToString(SystemErrorCode error_code) {
+#if defined(OS_WIN)
+  const int kErrorMessageBufferSize = 256;
+  char msgbuf[kErrorMessageBufferSize];
+  DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
+  DWORD len = FormatMessageA(flags, nullptr, error_code, 0, msgbuf,
+                             std::size(msgbuf), nullptr);
+  if (len) {
+    // Messages returned by system end with line breaks.
+    return base::CollapseWhitespaceASCII(msgbuf, true) +
+           base::StringPrintf(" (0x%lX)", error_code);
+  }
+  return base::StringPrintf("Error (0x%lX) while retrieving error. (0x%lX)",
+                            GetLastError(), error_code);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+  return base::safe_strerror(error_code) +
+         base::StringPrintf(" (%d)", error_code);
+#endif  // defined(OS_WIN)
+}
+
+#if defined(OS_WIN)
+Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file,
+                                           int line,
+                                           LogSeverity severity,
+                                           SystemErrorCode err)
+    : err_(err), log_message_(file, line, severity) {}
+
+Win32ErrorLogMessage::~Win32ErrorLogMessage() {
+  stream() << ": " << SystemErrorCodeToString(err_);
+}
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ErrnoLogMessage::ErrnoLogMessage(const char* file,
+                                 int line,
+                                 LogSeverity severity,
+                                 SystemErrorCode err)
+    : err_(err), log_message_(file, line, severity) {}
+
+ErrnoLogMessage::~ErrnoLogMessage() {
+  stream() << ": " << SystemErrorCodeToString(err_);
+}
+#endif  // defined(OS_WIN)
+
+void RawLog(int level, const char* message) {
+  if (level >= g_min_log_level && message) {
+    size_t bytes_written = 0;
+    const size_t message_len = strlen(message);
+    int rv;
+    while (bytes_written < message_len) {
+      rv = HANDLE_EINTR(write(STDERR_FILENO, message + bytes_written,
+                              message_len - bytes_written));
+      if (rv < 0) {
+        // Give up, nothing we can do now.
+        break;
+      }
+      bytes_written += rv;
+    }
+
+    if (message_len > 0 && message[message_len - 1] != '\n') {
+      do {
+        rv = HANDLE_EINTR(write(STDERR_FILENO, "\n", 1));
+        if (rv < 0) {
+          // Give up, nothing we can do now.
+          break;
+        }
+      } while (rv != 1);
+    }
+  }
+
+  if (level == LOG_FATAL)
+    abort();
+}
+
+// This was defined at the beginning of this file.
+#undef write
+
+void LogErrorNotReached(const char* file, int line) {
+  LogMessage(file, line, LOG_ERROR).stream() << "NOTREACHED() hit.";
+}
+
+}  // namespace logging
diff --git a/src/base/logging.h b/src/base/logging.h
new file mode 100644 (file)
index 0000000..78cad59
--- /dev/null
@@ -0,0 +1,922 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_LOGGING_H_
+#define BASE_LOGGING_H_
+
+#include <stddef.h>
+
+#include <cassert>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/template_util.h"
+#include "util/build_config.h"
+
+//
+// Optional message capabilities
+// -----------------------------
+// Assertion failed messages and fatal errors are displayed in a dialog box
+// before the application exits. However, running this UI creates a message
+// loop, which causes application messages to be processed and potentially
+// dispatched to existing application windows. Since the application is in a
+// bad state when this assertion dialog is displayed, these messages may not
+// get processed and hang the dialog, or the application might go crazy.
+//
+// Therefore, it can be beneficial to display the error dialog in a separate
+// process from the main application. When the logging system needs to display
+// a fatal error dialog box, it will look for a program called
+// "DebugMessage.exe" in the same directory as the application executable. It
+// will run this application with the message as the command line, and will
+// not include the name of the application as is traditional for easier
+// parsing.
+//
+// The code for DebugMessage.exe is only one line. In WinMain, do:
+//   MessageBox(NULL, GetCommandLineW(), L"Fatal Error", 0);
+//
+// If DebugMessage.exe is not found, the logging code will use a normal
+// MessageBox, potentially causing the problems discussed above.
+
+// Instructions
+// ------------
+//
+// Make a bunch of macros for logging.  The way to log things is to stream
+// things to LOG(<a particular severity level>).  E.g.,
+//
+//   LOG(INFO) << "Found " << num_cookies << " cookies";
+//
+// You can also do conditional logging:
+//
+//   LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
+//
+// The CHECK(condition) macro is active in both debug and release builds and
+// effectively performs a LOG(FATAL) which terminates the process and
+// generates a crashdump unless a debugger is attached.
+//
+// There are also "debug mode" logging macros like the ones above:
+//
+//   DLOG(INFO) << "Found cookies";
+//
+//   DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
+//
+// All "debug mode" logging is compiled away to nothing for non-debug mode
+// compiles.  LOG_IF and development flags also work well together
+// because the code can be compiled away sometimes.
+//
+// We also have
+//
+//   LOG_ASSERT(assertion);
+//   DLOG_ASSERT(assertion);
+//
+// which is syntactic sugar for {,D}LOG_IF(FATAL, assert fails) << assertion;
+//
+// We also override the standard 'assert' to use 'DLOG_ASSERT'.
+//
+// Lastly, there is:
+//
+//   PLOG(ERROR) << "Couldn't do foo";
+//   DPLOG(ERROR) << "Couldn't do foo";
+//   PLOG_IF(ERROR, cond) << "Couldn't do foo";
+//   DPLOG_IF(ERROR, cond) << "Couldn't do foo";
+//   PCHECK(condition) << "Couldn't do foo";
+//   DPCHECK(condition) << "Couldn't do foo";
+//
+// which append the last system error to the message in string form (taken from
+// GetLastError() on Windows and errno on POSIX).
+//
+// The supported severity levels for macros that allow you to specify one
+// are (in increasing order of severity) INFO, WARNING, ERROR, and FATAL.
+//
+// Very important: logging a message at the FATAL severity level causes
+// the program to terminate (after the message is logged).
+//
+// There is the special severity of DFATAL, which logs FATAL in debug mode,
+// ERROR in normal mode.
+
+namespace logging {
+
+// Sets the log level. Anything at or above this level will be written to the
+// log file/displayed to the user (if applicable). Anything below this level
+// will be silently ignored. The log level defaults to 0 (everything is logged
+// up to level INFO) if this function is not called.
+void SetMinLogLevel(int level);
+
+// Gets the current log level.
+int GetMinLogLevel();
+
+// Used by LOG_IS_ON to lazy-evaluate stream arguments.
+bool ShouldCreateLogMessage(int severity);
+
+// The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints
+// to Clang which control what code paths are statically analyzed,
+// and is meant to be used in conjunction with assert & assert-like functions.
+// The expression is passed straight through if analysis isn't enabled.
+//
+// ANALYZER_SKIP_THIS_PATH() suppresses static analysis for the current
+// codepath and any other branching codepaths that might follow.
+#if defined(__clang_analyzer__)
+
+inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
+  return false;
+}
+
+inline constexpr bool AnalyzerAssumeTrue(bool arg) {
+  // AnalyzerNoReturn() is invoked and analysis is terminated if |arg| is
+  // false.
+  return arg || AnalyzerNoReturn();
+}
+
+#define ANALYZER_ASSUME_TRUE(arg) logging::AnalyzerAssumeTrue(!!(arg))
+#define ANALYZER_SKIP_THIS_PATH() \
+  static_cast<void>(::logging::AnalyzerNoReturn())
+#define ANALYZER_ALLOW_UNUSED(var) static_cast<void>(var);
+
+#else  // !defined(__clang_analyzer__)
+
+#define ANALYZER_ASSUME_TRUE(arg) (arg)
+#define ANALYZER_SKIP_THIS_PATH()
+#define ANALYZER_ALLOW_UNUSED(var) static_cast<void>(var);
+
+#endif  // defined(__clang_analyzer__)
+
+typedef int LogSeverity;
+const LogSeverity LOG_VERBOSE = -1;  // This is level 1 verbosity
+// Note: the log severities are used to index into the array of names,
+// see log_severity_names.
+const LogSeverity LOG_INFO = 0;
+const LogSeverity LOG_WARNING = 1;
+const LogSeverity LOG_ERROR = 2;
+const LogSeverity LOG_FATAL = 3;
+const LogSeverity LOG_NUM_SEVERITIES = 4;
+
+// LOG_DFATAL is LOG_FATAL in debug mode, ERROR in normal mode
+#if defined(NDEBUG)
+const LogSeverity LOG_DFATAL = LOG_ERROR;
+#else
+const LogSeverity LOG_DFATAL = LOG_FATAL;
+#endif
+
+// A few definitions of macros that don't generate much code. These are used
+// by LOG() and LOG_IF, etc. Since these are used all over our code, it's
+// better to have compact code for these operations.
+#define COMPACT_GOOGLE_LOG_EX_INFO(ClassName, ...) \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_INFO, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_WARNING(ClassName, ...)              \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_WARNING, \
+                       ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ...) \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_ERROR, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_FATAL(ClassName, ...) \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_FATAL, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_DFATAL(ClassName, ...) \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_DFATAL, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_DCHECK, ##__VA_ARGS__)
+
+#define COMPACT_GOOGLE_LOG_INFO COMPACT_GOOGLE_LOG_EX_INFO(LogMessage)
+#define COMPACT_GOOGLE_LOG_WARNING COMPACT_GOOGLE_LOG_EX_WARNING(LogMessage)
+#define COMPACT_GOOGLE_LOG_ERROR COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage)
+#define COMPACT_GOOGLE_LOG_FATAL COMPACT_GOOGLE_LOG_EX_FATAL(LogMessage)
+#define COMPACT_GOOGLE_LOG_DFATAL COMPACT_GOOGLE_LOG_EX_DFATAL(LogMessage)
+#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_EX_DCHECK(LogMessage)
+
+#if defined(OS_WIN)
+// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets
+// substituted with 0, and it expands to COMPACT_GOOGLE_LOG_0. To allow us
+// to keep using this syntax, we define this macro to do the same thing
+// as COMPACT_GOOGLE_LOG_ERROR, and also define ERROR the same way that
+// the Windows SDK does for consistency.
+#define ERROR 0
+#define COMPACT_GOOGLE_LOG_EX_0(ClassName, ...) \
+  COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_0 COMPACT_GOOGLE_LOG_ERROR
+// Needed for LOG_IS_ON(ERROR).
+const LogSeverity LOG_0 = LOG_ERROR;
+#endif
+
+// As special cases, we can assume that LOG_IS_ON(FATAL) always holds. Also,
+// LOG_IS_ON(DFATAL) always holds in debug mode. In particular, CHECK()s will
+// always fire if they fail.
+#define LOG_IS_ON(severity) \
+  (::logging::ShouldCreateLogMessage(::logging::LOG_##severity))
+
+// Helper macro which avoids evaluating the arguments to a stream if
+// the condition doesn't hold. Condition is evaluated once and only once.
+#define LAZY_STREAM(stream, condition) \
+  !(condition) ? (void)0 : ::logging::LogMessageVoidify() & (stream)
+
+// We use the preprocessor's merging operator, "##", so that, e.g.,
+// LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO.  There's some funny
+// subtle difference between ostream member streaming functions (e.g.,
+// ostream::operator<<(int) and ostream non-member streaming functions
+// (e.g., ::operator<<(ostream&, string&): it turns out that it's
+// impossible to stream something like a string directly to an unnamed
+// ostream. We employ a neat hack by calling the stream() member
+// function of LogMessage which seems to avoid the problem.
+#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_##severity.stream()
+
+#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))
+#define LOG_IF(severity, condition) \
+  LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity) && (condition))
+
+#define LOG_ASSERT(condition)                       \
+  LOG_IF(FATAL, !(ANALYZER_ASSUME_TRUE(condition))) \
+      << "Assert failed: " #condition ". "
+
+#if defined(OS_WIN)
+#define PLOG_STREAM(severity)                                           \
+  COMPACT_GOOGLE_LOG_EX_##severity(Win32ErrorLogMessage,                \
+                                   ::logging::GetLastSystemErrorCode()) \
+      .stream()
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define PLOG_STREAM(severity)                                           \
+  COMPACT_GOOGLE_LOG_EX_##severity(ErrnoLogMessage,                     \
+                                   ::logging::GetLastSystemErrorCode()) \
+      .stream()
+#endif
+
+#define PLOG(severity) LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity))
+
+#define PLOG_IF(severity, condition) \
+  LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity) && (condition))
+
+extern std::ostream* g_swallow_stream;
+
+// Note that g_swallow_stream is used instead of an arbitrary LOG() stream to
+// avoid the creation of an object with a non-trivial destructor (LogMessage).
+// On MSVC x86 (checked on 2015 Update 3), this causes a few additional
+// pointless instructions to be emitted even at full optimization level, even
+// though the : arm of the ternary operator is clearly never executed. Using a
+// simpler object to be &'d with Voidify() avoids these extra instructions.
+// Using a simpler POD object with a templated operator<< also works to avoid
+// these instructions. However, this causes warnings on statically defined
+// implementations of operator<<(std::ostream, ...) in some .cc files, because
+// they become defined-but-unreferenced functions. A reinterpret_cast of 0 to an
+// ostream* also is not suitable, because some compilers warn of undefined
+// behavior.
+#define EAT_STREAM_PARAMETERS \
+  true ? (void)0              \
+       : ::logging::LogMessageVoidify() & (*::logging::g_swallow_stream)
+
+// Captures the result of a CHECK_EQ (for example) and facilitates testing as a
+// boolean.
+class CheckOpResult {
+ public:
+  // |message| must be non-null if and only if the check failed.
+  CheckOpResult(std::string* message) : message_(message) {}
+  // Returns true if the check succeeded.
+  operator bool() const { return !message_; }
+  // Returns the message.
+  std::string* message() { return message_; }
+
+ private:
+  std::string* message_;
+};
+
+// Crashes in the fastest possible way with no attempt at logging.
+// There are different constraints to satisfy here, see http://crbug.com/664209
+// for more context:
+// - The trap instructions, and hence the PC value at crash time, have to be
+//   distinct and not get folded into the same opcode by the compiler.
+//   On Linux/Android this is tricky because GCC still folds identical
+//   asm volatile blocks. The workaround is generating distinct opcodes for
+//   each CHECK using the __COUNTER__ macro.
+// - The debug info for the trap instruction has to be attributed to the source
+//   line that has the CHECK(), to make crash reports actionable. This rules
+//   out the ability of using a inline function, at least as long as clang
+//   doesn't support attribute(artificial).
+// - Failed CHECKs should produce a signal that is distinguishable from an
+//   invalid memory access, to improve the actionability of crash reports.
+// - The compiler should treat the CHECK as no-return instructions, so that the
+//   trap code can be efficiently packed in the prologue of the function and
+//   doesn't interfere with the main execution flow.
+// - When debugging, developers shouldn't be able to accidentally step over a
+//   CHECK. This is achieved by putting opcodes that will cause a non
+//   continuable exception after the actual trap instruction.
+// - Don't cause too much binary bloat.
+#if defined(COMPILER_GCC)
+
+#if defined(ARCH_CPU_X86_FAMILY)
+// int 3 will generate a SIGTRAP.
+#define TRAP_SEQUENCE() \
+  asm volatile(         \
+      "int3; ud2; push %0;" ::"i"(static_cast<unsigned char>(__COUNTER__)))
+
+#elif defined(ARCH_CPU_ARMEL)
+// bkpt will generate a SIGBUS when running on armv7 and a SIGTRAP when running
+// as a 32 bit userspace app on arm64. There doesn't seem to be any way to
+// cause a SIGTRAP from userspace without using a syscall (which would be a
+// problem for sandboxing).
+#define TRAP_SEQUENCE() \
+  asm volatile("bkpt #0; udf %0;" ::"i"(__COUNTER__ % 256))
+
+#elif defined(ARCH_CPU_ARM64)
+// This will always generate a SIGTRAP on arm64.
+#define TRAP_SEQUENCE() \
+  asm volatile("brk #0; hlt %0;" ::"i"(__COUNTER__ % 65536))
+
+#else
+// Crash report accuracy will not be guaranteed on other architectures, but at
+// least this will crash as expected.
+#define TRAP_SEQUENCE() __builtin_trap()
+#endif  // ARCH_CPU_*
+
+// CHECK() and the trap sequence can be invoked from a constexpr function.
+// This could make compilation fail on GCC, as it forbids directly using inline
+// asm inside a constexpr function. However, it allows calling a lambda
+// expression including the same asm.
+// The side effect is that the top of the stacktrace will not point to the
+// calling function, but to this anonymous lambda. This is still useful as the
+// full name of the lambda will typically include the name of the function that
+// calls CHECK() and the debugger will still break at the right line of code.
+#if !defined(__clang__)
+#define WRAPPED_TRAP_SEQUENCE() \
+  do {                          \
+    [] { TRAP_SEQUENCE(); }();  \
+  } while (false)
+#else
+#define WRAPPED_TRAP_SEQUENCE() TRAP_SEQUENCE()
+#endif
+
+#define IMMEDIATE_CRASH()    \
+  ({                         \
+    WRAPPED_TRAP_SEQUENCE(); \
+    __builtin_unreachable(); \
+  })
+
+#elif defined(COMPILER_MSVC)
+
+// Clang is cleverer about coalescing int3s, so we need to add a unique-ish
+// instruction following the __debugbreak() to have it emit distinct locations
+// for CHECKs rather than collapsing them all together. It would be nice to use
+// a short intrinsic to do this (and perhaps have only one implementation for
+// both clang and MSVC), however clang-cl currently does not support intrinsics.
+// On the flip side, MSVC x64 doesn't support inline asm. So, we have to have
+// two implementations. Normally clang-cl's version will be 5 bytes (1 for
+// `int3`, 2 for `ud2`, 2 for `push byte imm`, however, TODO(scottmg):
+// https://crbug.com/694670 clang-cl doesn't currently support %'ing
+// __COUNTER__, so eventually it will emit the dword form of push.
+// TODO(scottmg): Reinvestigate a short sequence that will work on both
+// compilers once clang supports more intrinsics. See https://crbug.com/693713.
+#if defined(__clang__)
+#define IMMEDIATE_CRASH()                           \
+  ({                                                \
+    {__asm int 3 __asm ud2 __asm push __COUNTER__}; \
+    __builtin_unreachable();                        \
+  })
+#else
+#define IMMEDIATE_CRASH() __debugbreak()
+#endif  // __clang__
+
+#else
+#error Port
+#endif
+
+// CHECK dies with a fatal error if condition is not true.  It is *not*
+// controlled by NDEBUG, so the check will be executed regardless of
+// compilation mode.
+//
+// We make sure CHECK et al. always evaluates their arguments, as
+// doing CHECK(FunctionWithSideEffect()) is a common idiom.
+
+#if defined(OFFICIAL_BUILD) && defined(NDEBUG)
+
+// Make all CHECK functions discard their log strings to reduce code bloat, and
+// improve performance, for official release builds.
+//
+// This is not calling BreakDebugger since this is called frequently, and
+// calling an out-of-line function instead of a noreturn inline macro prevents
+// compiler optimizations.
+#define CHECK(condition) \
+  UNLIKELY(!(condition)) ? IMMEDIATE_CRASH() : EAT_STREAM_PARAMETERS
+
+// PCHECK includes the system error code, which is useful for determining
+// why the condition failed. In official builds, preserve only the error code
+// message so that it is available in crash reports. The stringified
+// condition and any additional stream parameters are dropped.
+#define PCHECK(condition)                                  \
+  LAZY_STREAM(PLOG_STREAM(FATAL), UNLIKELY(!(condition))); \
+  EAT_STREAM_PARAMETERS
+
+#define CHECK_OP(name, op, val1, val2) CHECK((val1)op(val2))
+
+#else  // !(OFFICIAL_BUILD && NDEBUG)
+
+#if defined(_PREFAST_) && defined(OS_WIN)
+// Use __analysis_assume to tell the VC++ static analysis engine that
+// assert conditions are true, to suppress warnings.  The LAZY_STREAM
+// parameter doesn't reference 'condition' in /analyze builds because
+// this evaluation confuses /analyze. The !! before condition is because
+// __analysis_assume gets confused on some conditions:
+// http://randomascii.wordpress.com/2011/09/13/analyze-for-visual-studio-the-ugly-part-5/
+
+#define CHECK(condition)                                                  \
+  __analysis_assume(!!(condition)), LAZY_STREAM(LOG_STREAM(FATAL), false) \
+                                        << "Check failed: " #condition ". "
+
+#define PCHECK(condition)                                                  \
+  __analysis_assume(!!(condition)), LAZY_STREAM(PLOG_STREAM(FATAL), false) \
+                                        << "Check failed: " #condition ". "
+
+#else  // _PREFAST_
+
+// Do as much work as possible out of line to reduce inline code size.
+#define CHECK(condition)                                                      \
+  LAZY_STREAM(::logging::LogMessage(__FILE__, __LINE__, #condition).stream(), \
+              !ANALYZER_ASSUME_TRUE(condition))
+
+#define PCHECK(condition)                                           \
+  LAZY_STREAM(PLOG_STREAM(FATAL), !ANALYZER_ASSUME_TRUE(condition)) \
+      << "Check failed: " #condition ". "
+
+#endif  // _PREFAST_
+
+// Helper macro for binary operators.
+// Don't use this macro directly in your code, use CHECK_EQ et al below.
+// The 'switch' is used to prevent the 'else' from being ambiguous when the
+// macro is used in an 'if' clause such as:
+// if (a == 1)
+//   CHECK_EQ(2, a);
+#define CHECK_OP(name, op, val1, val2)                                    \
+  switch (0)                                                              \
+  case 0:                                                                 \
+  default:                                                                \
+    if (::logging::CheckOpResult true_if_passed =                         \
+            ::logging::Check##name##Impl((val1), (val2),                  \
+                                         #val1 " " #op " " #val2))        \
+      ;                                                                   \
+    else                                                                  \
+      ::logging::LogMessage(__FILE__, __LINE__, true_if_passed.message()) \
+          .stream()
+
+#endif  // !(OFFICIAL_BUILD && NDEBUG)
+
+// This formats a value for a failing CHECK_XX statement.  Ordinarily,
+// it uses the definition for operator<<, with a few special cases below.
+template <typename T>
+inline typename std::enable_if<
+    base::internal::SupportsOstreamOperator<const T&>::value &&
+        !std::is_function<typename std::remove_pointer<T>::type>::value,
+    void>::type
+MakeCheckOpValueString(std::ostream* os, const T& v) {
+  (*os) << v;
+}
+
+// Provide an overload for functions and function pointers. Function pointers
+// don't implicitly convert to void* but do implicitly convert to bool, so
+// without this function pointers are always printed as 1 or 0. (MSVC isn't
+// standards-conforming here and converts function pointers to regular
+// pointers, so this is a no-op for MSVC.)
+template <typename T>
+inline typename std::enable_if<
+    std::is_function<typename std::remove_pointer<T>::type>::value,
+    void>::type
+MakeCheckOpValueString(std::ostream* os, const T& v) {
+  (*os) << reinterpret_cast<const void*>(v);
+}
+
+// We need overloads for enums that don't support operator<<.
+// (i.e. scoped enums where no operator<< overload was declared).
+template <typename T>
+inline typename std::enable_if<
+    !base::internal::SupportsOstreamOperator<const T&>::value &&
+        std::is_enum<T>::value,
+    void>::type
+MakeCheckOpValueString(std::ostream* os, const T& v) {
+  (*os) << static_cast<typename std::underlying_type<T>::type>(v);
+}
+
+// We need an explicit overload for std::nullptr_t.
+void MakeCheckOpValueString(std::ostream* os, std::nullptr_t p);
+
+// Build the error message string.  This is separate from the "Impl"
+// function template because it is not performance critical and so can
+// be out of line, while the "Impl" code should be inline.  Caller
+// takes ownership of the returned string.
+template <class t1, class t2>
+std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) {
+  std::ostringstream ss;
+  ss << names << " (";
+  MakeCheckOpValueString(&ss, v1);
+  ss << " vs. ";
+  MakeCheckOpValueString(&ss, v2);
+  ss << ")";
+  std::string* msg = new std::string(ss.str());
+  return msg;
+}
+
+// Commonly used instantiations of MakeCheckOpString<>. Explicitly instantiated
+// in logging.cc.
+extern template std::string* MakeCheckOpString<int, int>(const int&,
+                                                         const int&,
+                                                         const char* names);
+extern template std::string* MakeCheckOpString<unsigned long, unsigned long>(
+    const unsigned long&,
+    const unsigned long&,
+    const char* names);
+extern template std::string* MakeCheckOpString<unsigned long, unsigned int>(
+    const unsigned long&,
+    const unsigned int&,
+    const char* names);
+extern template std::string* MakeCheckOpString<unsigned int, unsigned long>(
+    const unsigned int&,
+    const unsigned long&,
+    const char* names);
+extern template std::string* MakeCheckOpString<std::string, std::string>(
+    const std::string&,
+    const std::string&,
+    const char* name);
+
+// Helper functions for CHECK_OP macro.
+// The (int, int) specialization works around the issue that the compiler
+// will not instantiate the template version of the function on values of
+// unnamed enum type - see comment below.
+//
+// The checked condition is wrapped with ANALYZER_ASSUME_TRUE, which under
+// static analysis builds, blocks analysis of the current path if the
+// condition is false.
+#define DEFINE_CHECK_OP_IMPL(name, op)                                       \
+  template <class t1, class t2>                                              \
+  inline std::string* Check##name##Impl(const t1& v1, const t2& v2,          \
+                                        const char* names) {                 \
+    if (ANALYZER_ASSUME_TRUE(v1 op v2))                                      \
+      return NULL;                                                           \
+    else                                                                     \
+      return ::logging::MakeCheckOpString(v1, v2, names);                    \
+  }                                                                          \
+  inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \
+    if (ANALYZER_ASSUME_TRUE(v1 op v2))                                      \
+      return NULL;                                                           \
+    else                                                                     \
+      return ::logging::MakeCheckOpString(v1, v2, names);                    \
+  }
+DEFINE_CHECK_OP_IMPL(EQ, ==)
+DEFINE_CHECK_OP_IMPL(NE, !=)
+DEFINE_CHECK_OP_IMPL(LE, <=)
+DEFINE_CHECK_OP_IMPL(LT, <)
+DEFINE_CHECK_OP_IMPL(GE, >=)
+DEFINE_CHECK_OP_IMPL(GT, >)
+#undef DEFINE_CHECK_OP_IMPL
+
+#define CHECK_EQ(val1, val2) CHECK_OP(EQ, ==, val1, val2)
+#define CHECK_NE(val1, val2) CHECK_OP(NE, !=, val1, val2)
+#define CHECK_LE(val1, val2) CHECK_OP(LE, <=, val1, val2)
+#define CHECK_LT(val1, val2) CHECK_OP(LT, <, val1, val2)
+#define CHECK_GE(val1, val2) CHECK_OP(GE, >=, val1, val2)
+#define CHECK_GT(val1, val2) CHECK_OP(GT, >, val1, val2)
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+#define DCHECK_IS_ON() 0
+#else
+#define DCHECK_IS_ON() 1
+#endif
+
+// Definitions for DLOG et al.
+
+#if DCHECK_IS_ON()
+
+#define DLOG_IS_ON(severity) LOG_IS_ON(severity)
+#define DLOG_IF(severity, condition) LOG_IF(severity, condition)
+#define DLOG_ASSERT(condition) LOG_ASSERT(condition)
+#define DPLOG_IF(severity, condition) PLOG_IF(severity, condition)
+
+#else  // DCHECK_IS_ON()
+
+// If !DCHECK_IS_ON(), we want to avoid emitting any references to |condition|
+// (which may reference a variable defined only if DCHECK_IS_ON()).
+// Contrast this with DCHECK et al., which has different behavior.
+
+#define DLOG_IS_ON(severity) false
+#define DLOG_IF(severity, condition) EAT_STREAM_PARAMETERS
+#define DLOG_ASSERT(condition) EAT_STREAM_PARAMETERS
+#define DPLOG_IF(severity, condition) EAT_STREAM_PARAMETERS
+
+#endif  // DCHECK_IS_ON()
+
+#define DLOG(severity) LAZY_STREAM(LOG_STREAM(severity), DLOG_IS_ON(severity))
+
+#define DPLOG(severity) LAZY_STREAM(PLOG_STREAM(severity), DLOG_IS_ON(severity))
+
+// Definitions for DCHECK et al.
+
+#if DCHECK_IS_ON()
+
+#if DCHECK_IS_CONFIGURABLE
+extern LogSeverity LOG_DCHECK;
+#else
+const LogSeverity LOG_DCHECK = LOG_FATAL;
+#endif
+
+#else  // DCHECK_IS_ON()
+
+// There may be users of LOG_DCHECK that are enabled independently
+// of DCHECK_IS_ON(), so default to FATAL logging for those.
+const LogSeverity LOG_DCHECK = LOG_FATAL;
+
+#endif  // DCHECK_IS_ON()
+
+// DCHECK et al. make sure to reference |condition| regardless of
+// whether DCHECKs are enabled; this is so that we don't get unused
+// variable warnings if the only use of a variable is in a DCHECK.
+// This behavior is different from DLOG_IF et al.
+//
+// Note that the definition of the DCHECK macros depends on whether or not
+// DCHECK_IS_ON() is true. When DCHECK_IS_ON() is false, the macros use
+// EAT_STREAM_PARAMETERS to avoid expressions that would create temporaries.
+
+#if defined(_PREFAST_) && defined(OS_WIN)
+// See comments on the previous use of __analysis_assume.
+
+#define DCHECK(condition)                                                  \
+  __analysis_assume(!!(condition)), LAZY_STREAM(LOG_STREAM(DCHECK), false) \
+                                        << "Check failed: " #condition ". "
+
+#define DPCHECK(condition)                                                  \
+  __analysis_assume(!!(condition)), LAZY_STREAM(PLOG_STREAM(DCHECK), false) \
+                                        << "Check failed: " #condition ". "
+
+#else  // !(defined(_PREFAST_) && defined(OS_WIN))
+
+#if DCHECK_IS_ON()
+
+#define DCHECK(condition)                                           \
+  LAZY_STREAM(LOG_STREAM(DCHECK), !ANALYZER_ASSUME_TRUE(condition)) \
+      << "Check failed: " #condition ". "
+#define DPCHECK(condition)                                           \
+  LAZY_STREAM(PLOG_STREAM(DCHECK), !ANALYZER_ASSUME_TRUE(condition)) \
+      << "Check failed: " #condition ". "
+
+#else  // DCHECK_IS_ON()
+
+#define DCHECK(condition) EAT_STREAM_PARAMETERS << !(condition)
+#define DPCHECK(condition) EAT_STREAM_PARAMETERS << !(condition)
+
+#endif  // DCHECK_IS_ON()
+
+#endif  // defined(_PREFAST_) && defined(OS_WIN)
+
+// Helper macro for binary operators.
+// Don't use this macro directly in your code, use DCHECK_EQ et al below.
+// The 'switch' is used to prevent the 'else' from being ambiguous when the
+// macro is used in an 'if' clause such as:
+// if (a == 1)
+//   DCHECK_EQ(2, a);
+#if DCHECK_IS_ON()
+
+#define DCHECK_OP(name, op, val1, val2)                                   \
+  switch (0)                                                              \
+  case 0:                                                                 \
+  default:                                                                \
+    if (::logging::CheckOpResult true_if_passed =                         \
+            DCHECK_IS_ON() ? ::logging::Check##name##Impl(                \
+                                 (val1), (val2), #val1 " " #op " " #val2) \
+                           : nullptr)                                     \
+      ;                                                                   \
+    else                                                                  \
+      ::logging::LogMessage(__FILE__, __LINE__, ::logging::LOG_DCHECK,    \
+                            true_if_passed.message())                     \
+          .stream()
+
+#else  // DCHECK_IS_ON()
+
+// When DCHECKs aren't enabled, DCHECK_OP still needs to reference operator<<
+// overloads for |val1| and |val2| to avoid potential compiler warnings about
+// unused functions. For the same reason, it also compares |val1| and |val2|
+// using |op|.
+//
+// Note that the contract of DCHECK_EQ, etc is that arguments are only evaluated
+// once. Even though |val1| and |val2| appear twice in this version of the macro
+// expansion, this is OK, since the expression is never actually evaluated.
+#define DCHECK_OP(name, op, val1, val2)                             \
+  EAT_STREAM_PARAMETERS << (::logging::MakeCheckOpValueString(      \
+                                ::logging::g_swallow_stream, val1), \
+                            ::logging::MakeCheckOpValueString(      \
+                                ::logging::g_swallow_stream, val2), \
+                            (val1)op(val2))
+
+#endif  // DCHECK_IS_ON()
+
+// Equality/Inequality checks - compare two values, and log a
+// LOG_DCHECK message including the two values when the result is not
+// as expected.  The values must have operator<<(ostream, ...)
+// defined.
+//
+// You may append to the error message like so:
+//   DCHECK_NE(1, 2) << "The world must be ending!";
+//
+// We are very careful to ensure that each argument is evaluated exactly
+// once, and that anything which is legal to pass as a function argument is
+// legal here.  In particular, the arguments may be temporary expressions
+// which will end up being destroyed at the end of the apparent statement,
+// for example:
+//   DCHECK_EQ(string("abc")[1], 'b');
+//
+// WARNING: These don't compile correctly if one of the arguments is a pointer
+// and the other is NULL.  In new code, prefer nullptr instead.  To
+// work around this for C++98, simply static_cast NULL to the type of the
+// desired pointer.
+
+#define DCHECK_EQ(val1, val2) DCHECK_OP(EQ, ==, val1, val2)
+#define DCHECK_NE(val1, val2) DCHECK_OP(NE, !=, val1, val2)
+#define DCHECK_LE(val1, val2) DCHECK_OP(LE, <=, val1, val2)
+#define DCHECK_LT(val1, val2) DCHECK_OP(LT, <, val1, val2)
+#define DCHECK_GE(val1, val2) DCHECK_OP(GE, >=, val1, val2)
+#define DCHECK_GT(val1, val2) DCHECK_OP(GT, >, val1, val2)
+
+#define NOTREACHED() DCHECK(false)
+
+// Redefine the standard assert to use our nice log files
+#undef assert
+#define assert(x) DLOG_ASSERT(x)
+
+// This class more or less represents a particular log message.  You
+// create an instance of LogMessage and then stream stuff to it.
+// When you finish streaming to it, ~LogMessage is called and the
+// full message gets streamed to the appropriate destination.
+//
+// You shouldn't actually use LogMessage's constructor to log things,
+// though.  You should use the LOG() macro (and variants thereof)
+// above.
+class LogMessage {
+ public:
+  // Used for LOG(severity).
+  LogMessage(const char* file, int line, LogSeverity severity);
+
+  // Used for CHECK().  Implied severity = LOG_FATAL.
+  LogMessage(const char* file, int line, const char* condition);
+
+  // Used for CHECK_EQ(), etc. Takes ownership of the given string.
+  // Implied severity = LOG_FATAL.
+  LogMessage(const char* file, int line, std::string* result);
+
+  // Used for DCHECK_EQ(), etc. Takes ownership of the given string.
+  LogMessage(const char* file,
+             int line,
+             LogSeverity severity,
+             std::string* result);
+
+  ~LogMessage();
+
+  std::ostream& stream() { return stream_; }
+
+  LogSeverity severity() { return severity_; }
+  std::string str() { return stream_.str(); }
+
+ private:
+  void Init(const char* file, int line);
+
+  LogSeverity severity_;
+  std::ostringstream stream_;
+  size_t message_start_;  // Offset of the start of the message (past prefix
+                          // info).
+
+#if defined(OS_WIN)
+  // Stores the current value of GetLastError in the constructor and restores
+  // it in the destructor by calling SetLastError.
+  // This is useful since the LogMessage class uses a lot of Win32 calls
+  // that will lose the value of GLE and the code that called the log function
+  // will have lost the thread error value when the log call returns.
+  class SaveLastError {
+   public:
+    SaveLastError();
+    ~SaveLastError();
+
+    unsigned long get_error() const { return last_error_; }
+
+   protected:
+    unsigned long last_error_;
+  };
+
+  SaveLastError last_error_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(LogMessage);
+};
+
+// This class is used to explicitly ignore values in the conditional
+// logging macros.  This avoids compiler warnings like "value computed
+// is not used" and "statement has no effect".
+class LogMessageVoidify {
+ public:
+  LogMessageVoidify() = default;
+  // This has to be an operator with a precedence lower than << but
+  // higher than ?:
+  void operator&(std::ostream&) {}
+};
+
+#if defined(OS_WIN)
+typedef unsigned long SystemErrorCode;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+typedef int SystemErrorCode;
+#endif
+
+// Alias for ::GetLastError() on Windows and errno on POSIX. Avoids having to
+// pull in windows.h just for GetLastError() and DWORD.
+SystemErrorCode GetLastSystemErrorCode();
+std::string SystemErrorCodeToString(SystemErrorCode error_code);
+
+#if defined(OS_WIN)
+// Appends a formatted system message of the GetLastError() type.
+class Win32ErrorLogMessage {
+ public:
+  Win32ErrorLogMessage(const char* file,
+                       int line,
+                       LogSeverity severity,
+                       SystemErrorCode err);
+
+  // Appends the error message before destructing the encapsulated class.
+  ~Win32ErrorLogMessage();
+
+  std::ostream& stream() { return log_message_.stream(); }
+
+ private:
+  SystemErrorCode err_;
+  LogMessage log_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(Win32ErrorLogMessage);
+};
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+// Appends a formatted system message of the errno type
+class ErrnoLogMessage {
+ public:
+  ErrnoLogMessage(const char* file,
+                  int line,
+                  LogSeverity severity,
+                  SystemErrorCode err);
+
+  // Appends the error message before destructing the encapsulated class.
+  ~ErrnoLogMessage();
+
+  std::ostream& stream() { return log_message_.stream(); }
+
+ private:
+  SystemErrorCode err_;
+  LogMessage log_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(ErrnoLogMessage);
+};
+#endif  // OS_WIN
+
+// Closes the log file explicitly if open.
+// NOTE: Since the log file is opened as necessary by the action of logging
+//       statements, there's no guarantee that it will stay closed
+//       after this call.
+void CloseLogFile();
+
+// Async signal safe logging mechanism.
+void RawLog(int level, const char* message);
+
+#define RAW_LOG(level, message) \
+  ::logging::RawLog(::logging::LOG_##level, message)
+
+#define RAW_CHECK(condition)                               \
+  do {                                                     \
+    if (!(condition))                                      \
+      ::logging::RawLog(::logging::LOG_FATAL,              \
+                        "Check failed: " #condition "\n"); \
+  } while (0)
+
+#if defined(OS_WIN)
+// Returns true if logging to file is enabled.
+bool IsLoggingToFileEnabled();
+
+// Returns the default log file path.
+std::u16string GetLogFileFullPath();
+#endif
+
+}  // namespace logging
+
+// The NOTIMPLEMENTED() macro annotates codepaths which have not been
+// implemented yet. If output spam is a serious concern,
+// NOTIMPLEMENTED_LOG_ONCE can be used.
+
+#if defined(COMPILER_GCC)
+// On Linux, with GCC, we can use __PRETTY_FUNCTION__ to get the demangled name
+// of the current function in the NOTIMPLEMENTED message.
+#define NOTIMPLEMENTED_MSG "Not implemented reached in " << __PRETTY_FUNCTION__
+#else
+#define NOTIMPLEMENTED_MSG "NOT IMPLEMENTED"
+#endif
+
+#if defined(OS_ANDROID) && defined(OFFICIAL_BUILD)
+#define NOTIMPLEMENTED() EAT_STREAM_PARAMETERS
+#define NOTIMPLEMENTED_LOG_ONCE() EAT_STREAM_PARAMETERS
+#else
+#define NOTIMPLEMENTED() LOG(ERROR) << NOTIMPLEMENTED_MSG
+#define NOTIMPLEMENTED_LOG_ONCE()                      \
+  do {                                                 \
+    static bool logged_once = false;                   \
+    LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG; \
+    logged_once = true;                                \
+  } while (0);                                         \
+  EAT_STREAM_PARAMETERS
+#endif
+
+#endif  // BASE_LOGGING_H_
diff --git a/src/base/mac/bundle_locations.h b/src/base/mac/bundle_locations.h
new file mode 100644 (file)
index 0000000..2bda76e
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MAC_BUNDLE_LOCATIONS_H_
+#define BASE_MAC_BUNDLE_LOCATIONS_H_
+
+#include "base/files/file_path.h"
+
+#if defined(__OBJC__)
+#import <Foundation/Foundation.h>
+#else   // __OBJC__
+class NSBundle;
+#endif  // __OBJC__
+
+namespace base {
+
+class FilePath;
+
+namespace mac {
+
+// This file provides several functions to explicitly request the various
+// component bundles of Chrome.  Please use these methods rather than calling
+// +[NSBundle mainBundle] or CFBundleGetMainBundle().
+//
+// Terminology
+//  - "Outer Bundle" - This is the main bundle for Chrome; it's what
+//  +[NSBundle mainBundle] returns when Chrome is launched normally.
+//
+//  - "Main Bundle" - This is the bundle from which Chrome was launched.
+//  This will be the same as the outer bundle except when Chrome is launched
+//  via an app shortcut, in which case this will return the app shortcut's
+//  bundle rather than the main Chrome bundle.
+//
+//  - "Framework Bundle" - This is the bundle corresponding to the Chrome
+//  framework.
+//
+// Guidelines for use:
+//  - To access a resource, the Framework bundle should be used.
+//  - If the choice is between the Outer or Main bundles then please choose
+//  carefully.  Most often the Outer bundle will be the right choice, but for
+//  cases such as adding an app to the "launch on startup" list, the Main
+//  bundle is probably the one to use.
+
+// Methods for retrieving the various bundles.
+NSBundle* MainBundle();
+FilePath MainBundlePath();
+NSBundle* OuterBundle();
+FilePath OuterBundlePath();
+NSBundle* FrameworkBundle();
+FilePath FrameworkBundlePath();
+
+// Set the bundle that the preceding functions will return, overriding the
+// default values. Restore the default by passing in |nil|.
+void SetOverrideOuterBundle(NSBundle* bundle);
+void SetOverrideFrameworkBundle(NSBundle* bundle);
+
+// Same as above but accepting a FilePath argument.
+void SetOverrideOuterBundlePath(const FilePath& file_path);
+void SetOverrideFrameworkBundlePath(const FilePath& file_path);
+
+}  // namespace mac
+}  // namespace base
+
+#endif  // BASE_MAC_BUNDLE_LOCATIONS_H_
diff --git a/src/base/mac/mac_logging.h b/src/base/mac/mac_logging.h
new file mode 100644 (file)
index 0000000..5ef75f3
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MAC_MAC_LOGGING_H_
+#define BASE_MAC_MAC_LOGGING_H_
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "util/build_config.h"
+
+#if defined(OS_IOS)
+#include <MacTypes.h>
+#else
+#include <libkern/OSTypes.h>
+#endif
+
+// Use the OSSTATUS_LOG family to log messages related to errors in Mac OS X
+// system routines that report status via an OSStatus or OSErr value. It is
+// similar to the PLOG family which operates on errno, but because there is no
+// global (or thread-local) OSStatus or OSErr value, the specific error must
+// be supplied as an argument to the OSSTATUS_LOG macro. The message logged
+// will contain the symbolic constant name corresponding to the status value,
+// along with the value itself.
+//
+// OSErr is just an older 16-bit form of the newer 32-bit OSStatus. Despite
+// the name, OSSTATUS_LOG can be used equally well for OSStatus and OSErr.
+
+namespace logging {
+
+// Returns a UTF8 description from an OS X Status error.
+std::string DescriptionFromOSStatus(OSStatus err);
+
+class OSStatusLogMessage : public logging::LogMessage {
+ public:
+  OSStatusLogMessage(const char* file_path,
+                     int line,
+                     LogSeverity severity,
+                     OSStatus status);
+  ~OSStatusLogMessage();
+
+ private:
+  OSStatus status_;
+
+  DISALLOW_COPY_AND_ASSIGN(OSStatusLogMessage);
+};
+
+}  // namespace logging
+
+#define OSSTATUS_LOG_STREAM(severity, status) \
+  COMPACT_GOOGLE_LOG_EX_##severity(OSStatusLogMessage, status).stream()
+
+#define OSSTATUS_LOG(severity, status) \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), LOG_IS_ON(severity))
+#define OSSTATUS_LOG_IF(severity, condition, status) \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), \
+              LOG_IS_ON(severity) && (condition))
+
+#define OSSTATUS_CHECK(condition, status)                       \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(FATAL, status), !(condition)) \
+      << "Check failed: " #condition << ". "
+
+#define OSSTATUS_DLOG(severity, status) \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), DLOG_IS_ON(severity))
+#define OSSTATUS_DLOG_IF(severity, condition, status) \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status),  \
+              DLOG_IS_ON(severity) && (condition))
+
+#define OSSTATUS_DCHECK(condition, status)        \
+  LAZY_STREAM(OSSTATUS_LOG_STREAM(FATAL, status), \
+              DCHECK_IS_ON() && !(condition))     \
+      << "Check failed: " #condition << ". "
+
+#endif  // BASE_MAC_MAC_LOGGING_H_
diff --git a/src/base/mac/mac_logging.mm b/src/base/mac/mac_logging.mm
new file mode 100644 (file)
index 0000000..f7c3052
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/mac/mac_logging.h"
+
+#import <Foundation/Foundation.h>
+
+#include <iomanip>
+
+#include "util/build_config.h"
+
+#if !defined(OS_IOS)
+#include <CoreServices/CoreServices.h>
+#endif
+
+namespace logging {
+
+std::string DescriptionFromOSStatus(OSStatus err) {
+  NSError* error =
+      [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
+  return error.description.UTF8String;
+}
+
+OSStatusLogMessage::OSStatusLogMessage(const char* file_path,
+                                       int line,
+                                       LogSeverity severity,
+                                       OSStatus status)
+    : LogMessage(file_path, line, severity), status_(status) {}
+
+OSStatusLogMessage::~OSStatusLogMessage() {
+#if defined(OS_IOS)
+  // TODO(crbug.com/546375): Consider using NSError with NSOSStatusErrorDomain
+  // to try to get a description of the failure.
+  stream() << ": " << status_;
+#else
+  stream() << ": " << DescriptionFromOSStatus(status_) << " (" << status_
+           << ")";
+#endif
+}
+
+}  // namespace logging
diff --git a/src/base/mac/scoped_cftyperef.h b/src/base/mac/scoped_cftyperef.h
new file mode 100644 (file)
index 0000000..a602fd9
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MAC_SCOPED_CFTYPEREF_H_
+#define BASE_MAC_SCOPED_CFTYPEREF_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/mac/scoped_typeref.h"
+
+namespace base {
+
+// ScopedCFTypeRef<> is patterned after std::unique_ptr<>, but maintains
+// ownership of a CoreFoundation object: any object that can be represented
+// as a CFTypeRef.  Style deviations here are solely for compatibility with
+// std::unique_ptr<>'s interface, with which everyone is already familiar.
+//
+// By default, ScopedCFTypeRef<> takes ownership of an object (in the
+// constructor or in reset()) by taking over the caller's existing ownership
+// claim.  The caller must own the object it gives to ScopedCFTypeRef<>, and
+// relinquishes an ownership claim to that object.  ScopedCFTypeRef<> does not
+// call CFRetain(). This behavior is parameterized by the |OwnershipPolicy|
+// enum. If the value |RETAIN| is passed (in the constructor or in reset()),
+// then ScopedCFTypeRef<> will call CFRetain() on the object, and the initial
+// ownership is not changed.
+
+namespace internal {
+
+template <typename CFT>
+struct ScopedCFTypeRefTraits {
+  static CFT InvalidValue() { return nullptr; }
+  static CFT Retain(CFT object) {
+    CFRetain(object);
+    return object;
+  }
+  static void Release(CFT object) { CFRelease(object); }
+};
+
+}  // namespace internal
+
+template <typename CFT>
+using ScopedCFTypeRef =
+    ScopedTypeRef<CFT, internal::ScopedCFTypeRefTraits<CFT>>;
+
+}  // namespace base
+
+#endif  // BASE_MAC_SCOPED_CFTYPEREF_H_
diff --git a/src/base/mac/scoped_typeref.h b/src/base/mac/scoped_typeref.h
new file mode 100644 (file)
index 0000000..659ee34
--- /dev/null
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MAC_SCOPED_TYPEREF_H_
+#define BASE_MAC_SCOPED_TYPEREF_H_
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_policy.h"
+
+namespace base {
+
+// ScopedTypeRef<> is patterned after std::unique_ptr<>, but maintains ownership
+// of a reference to any type that is maintained by Retain and Release methods.
+//
+// The Traits structure must provide the Retain and Release methods for type T.
+// A default ScopedTypeRefTraits is used but not defined, and should be defined
+// for each type to use this interface. For example, an appropriate definition
+// of ScopedTypeRefTraits for CGLContextObj would be:
+//
+//   template<>
+//   struct ScopedTypeRefTraits<CGLContextObj> {
+//     static CGLContextObj InvalidValue() { return nullptr; }
+//     static CGLContextObj Retain(CGLContextObj object) {
+//       CGLContextRetain(object);
+//       return object;
+//     }
+//     static void Release(CGLContextObj object) { CGLContextRelease(object); }
+//   };
+//
+// For the many types that have pass-by-pointer create functions, the function
+// InitializeInto() is provided to allow direct initialization and assumption
+// of ownership of the object. For example, continuing to use the above
+// CGLContextObj specialization:
+//
+//   base::ScopedTypeRef<CGLContextObj> context;
+//   CGLCreateContext(pixel_format, share_group, context.InitializeInto());
+//
+// For initialization with an existing object, the caller may specify whether
+// the ScopedTypeRef<> being initialized is assuming the caller's existing
+// ownership of the object (and should not call Retain in initialization) or if
+// it should not assume this ownership and must create its own (by calling
+// Retain in initialization). This behavior is based on the |policy| parameter,
+// with |ASSUME| for the former and |RETAIN| for the latter. The default policy
+// is to |ASSUME|.
+
+template <typename T>
+struct ScopedTypeRefTraits;
+
+template <typename T, typename Traits = ScopedTypeRefTraits<T>>
+class ScopedTypeRef {
+ public:
+  typedef T element_type;
+
+  explicit constexpr ScopedTypeRef(
+      __unsafe_unretained T object = Traits::InvalidValue(),
+      base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME)
+      : object_(object) {
+    if (object_ && policy == base::scoped_policy::RETAIN)
+      object_ = Traits::Retain(object_);
+  }
+
+  ScopedTypeRef(const ScopedTypeRef<T, Traits>& that) : object_(that.object_) {
+    if (object_)
+      object_ = Traits::Retain(object_);
+  }
+
+  // This allows passing an object to a function that takes its superclass.
+  template <typename R, typename RTraits>
+  explicit ScopedTypeRef(const ScopedTypeRef<R, RTraits>& that_as_subclass)
+      : object_(that_as_subclass.get()) {
+    if (object_)
+      object_ = Traits::Retain(object_);
+  }
+
+  ScopedTypeRef(ScopedTypeRef<T, Traits>&& that) : object_(that.object_) {
+    that.object_ = Traits::InvalidValue();
+  }
+
+  ~ScopedTypeRef() {
+    if (object_)
+      Traits::Release(object_);
+  }
+
+  ScopedTypeRef& operator=(const ScopedTypeRef<T, Traits>& that) {
+    reset(that.get(), base::scoped_policy::RETAIN);
+    return *this;
+  }
+
+  // This is to be used only to take ownership of objects that are created
+  // by pass-by-pointer create functions. To enforce this, require that the
+  // object be reset to NULL before this may be used.
+  T* InitializeInto() WARN_UNUSED_RESULT {
+    DCHECK(!object_);
+    return &object_;
+  }
+
+  void reset(__unsafe_unretained T object = Traits::InvalidValue(),
+             base::scoped_policy::OwnershipPolicy policy =
+                 base::scoped_policy::ASSUME) {
+    if (object && policy == base::scoped_policy::RETAIN)
+      object = Traits::Retain(object);
+    if (object_)
+      Traits::Release(object_);
+    object_ = object;
+  }
+
+  bool operator==(__unsafe_unretained T that) const { return object_ == that; }
+
+  bool operator!=(__unsafe_unretained T that) const { return object_ != that; }
+
+  operator T() const __attribute((ns_returns_not_retained)) { return object_; }
+
+  T get() const __attribute((ns_returns_not_retained)) { return object_; }
+
+  void swap(ScopedTypeRef& that) {
+    __unsafe_unretained T temp = that.object_;
+    that.object_ = object_;
+    object_ = temp;
+  }
+
+  // ScopedTypeRef<>::release() is like std::unique_ptr<>::release.  It is NOT
+  // a wrapper for Release().  To force a ScopedTypeRef<> object to call
+  // Release(), use ScopedTypeRef<>::reset().
+  T release() __attribute((ns_returns_not_retained)) WARN_UNUSED_RESULT {
+    __unsafe_unretained T temp = object_;
+    object_ = Traits::InvalidValue();
+    return temp;
+  }
+
+ private:
+  __unsafe_unretained T object_;
+};
+
+}  // namespace base
+
+#endif  // BASE_MAC_SCOPED_TYPEREF_H_
diff --git a/src/base/macros.h b/src/base/macros.h
new file mode 100644 (file)
index 0000000..750d54d
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains macros and macro-like constructs (e.g., templates) that
+// are commonly used throughout Chromium source. (It may also contain things
+// that are closely related to things that are commonly used that belong in this
+// file.)
+
+#ifndef BASE_MACROS_H_
+#define BASE_MACROS_H_
+
+// Distinguish mips32.
+#if defined(__mips__) && (_MIPS_SIM == _ABIO32) && !defined(__mips32__)
+#define __mips32__
+#endif
+
+// Distinguish mips64.
+#if defined(__mips__) && (_MIPS_SIM == _ABI64) && !defined(__mips64__)
+#define __mips64__
+#endif
+
+// Put this in the declarations for a class to be uncopyable.
+#define DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete
+
+// Put this in the declarations for a class to be unassignable.
+#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete
+
+// Put this in the declarations for a class to be uncopyable and unassignable.
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  DISALLOW_COPY(TypeName);                 \
+  DISALLOW_ASSIGN(TypeName)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+// This is especially useful for classes containing only static methods.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName() = delete;                           \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// Used to explicitly mark the return value of a function as unused. If you are
+// really sure you don't want to do anything with the return value of a function
+// that has been marked WARN_UNUSED_RESULT, wrap it with this. Example:
+//
+//   std::unique_ptr<MyType> my_var = ...;
+//   if (TakeOwnership(my_var.get()) == SUCCESS)
+//     ignore_result(my_var.release());
+//
+template <typename T>
+inline void ignore_result(const T&) {}
+
+#endif  // BASE_MACROS_H_
diff --git a/src/base/md5.cc b/src/base/md5.cc
new file mode 100644 (file)
index 0000000..4fcab55
--- /dev/null
@@ -0,0 +1,301 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The original file was copied from sqlite, and was in the public domain.
+
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include "base/md5.h"
+
+#include <stddef.h>
+#include <string.h>
+
+namespace {
+
+struct Context {
+  uint32_t buf[4];
+  uint32_t bits[2];
+  uint8_t in[64];
+};
+
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(uint8_t* buf, unsigned longs) {
+  do {
+    uint32_t temp =
+        static_cast<uint32_t>(static_cast<unsigned>(buf[3]) << 8 | buf[2])
+            << 16 |
+        (static_cast<unsigned>(buf[1]) << 8 | buf[0]);
+    *reinterpret_cast<uint32_t*>(buf) = temp;
+    buf += 4;
+  } while (--longs);
+}
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void MD5Transform(uint32_t buf[4], const uint32_t in[16]) {
+  uint32_t a, b, c, d;
+
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
+
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+}  // namespace
+
+namespace base {
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(MD5Context* context) {
+  struct Context* ctx = reinterpret_cast<struct Context*>(context);
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(MD5Context* context, const std::string_view& data) {
+  struct Context* ctx = reinterpret_cast<struct Context*>(context);
+  const uint8_t* buf = reinterpret_cast<const uint8_t*>(data.data());
+  size_t len = data.size();
+
+  /* Update bitcount */
+
+  uint32_t t = ctx->bits[0];
+  if ((ctx->bits[0] = t + (static_cast<uint32_t>(len) << 3)) < t)
+    ctx->bits[1]++; /* Carry from low to high */
+  ctx->bits[1] += static_cast<uint32_t>(len >> 29);
+
+  t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+  /* Handle any leading odd-sized chunks */
+
+  if (t) {
+    uint8_t* p = static_cast<uint8_t*>(ctx->in + t);
+
+    t = 64 - t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+    buf += t;
+    len -= t;
+  }
+
+  /* Process data in 64-byte chunks */
+
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+    buf += 64;
+    len -= 64;
+  }
+
+  /* Handle any remaining bytes of data. */
+
+  memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(MD5Digest* digest, MD5Context* context) {
+  struct Context* ctx = reinterpret_cast<struct Context*>(context);
+  unsigned count;
+  uint8_t* p;
+
+  /* Compute number of bytes mod 64 */
+  count = (ctx->bits[0] >> 3) & 0x3F;
+
+  /* Set the first char of padding to 0x80.  This is safe since there is
+     always at least one byte free */
+  p = ctx->in + count;
+  *p++ = 0x80;
+
+  /* Bytes of padding needed to make 64 bytes */
+  count = 64 - 1 - count;
+
+  /* Pad out to 56 mod 64 */
+  if (count < 8) {
+    /* Two lots of padding:  Pad the first block to 64 bytes */
+    memset(p, 0, count);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+
+    /* Now fill the next block with 56 bytes */
+    memset(ctx->in, 0, 56);
+  } else {
+    /* Pad block to 56 bytes */
+    memset(p, 0, count - 8);
+  }
+  byteReverse(ctx->in, 14);
+
+  /* Append length in bits and transform */
+  memcpy(&ctx->in[14 * sizeof(ctx->bits[0])], &ctx->bits[0],
+         sizeof(ctx->bits[0]));
+  memcpy(&ctx->in[15 * sizeof(ctx->bits[1])], &ctx->bits[1],
+         sizeof(ctx->bits[1]));
+
+  MD5Transform(ctx->buf, reinterpret_cast<uint32_t*>(ctx->in));
+  byteReverse(reinterpret_cast<uint8_t*>(ctx->buf), 4);
+  memcpy(digest->a, ctx->buf, 16);
+  memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+void MD5IntermediateFinal(MD5Digest* digest, const MD5Context* context) {
+  /* MD5Final mutates the MD5Context*. Make a copy for generating the
+     intermediate value. */
+  MD5Context context_copy;
+  memcpy(&context_copy, context, sizeof(context_copy));
+  MD5Final(digest, &context_copy);
+}
+
+std::string MD5DigestToBase16(const MD5Digest& digest) {
+  static char const zEncode[] = "0123456789abcdef";
+
+  std::string ret;
+  ret.resize(32);
+
+  for (int i = 0, j = 0; i < 16; i++, j += 2) {
+    uint8_t a = digest.a[i];
+    ret[j] = zEncode[(a >> 4) & 0xf];
+    ret[j + 1] = zEncode[a & 0xf];
+  }
+  return ret;
+}
+
+void MD5Sum(const void* data, size_t length, MD5Digest* digest) {
+  MD5Context ctx;
+  MD5Init(&ctx);
+  MD5Update(&ctx,
+            std::string_view(reinterpret_cast<const char*>(data), length));
+  MD5Final(digest, &ctx);
+}
+
+std::string MD5String(const std::string_view& str) {
+  MD5Digest digest;
+  MD5Sum(str.data(), str.length(), &digest);
+  return MD5DigestToBase16(digest);
+}
+
+}  // namespace base
diff --git a/src/base/md5.h b/src/base/md5.h
new file mode 100644 (file)
index 0000000..d25b2fc
--- /dev/null
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MD5_H_
+#define BASE_MD5_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <string_view>
+
+namespace base {
+
+// MD5 stands for Message Digest algorithm 5.
+// MD5 is a robust hash function, designed for cyptography, but often used
+// for file checksums.  The code is complex and slow, but has few
+// collisions.
+// See Also:
+//   http://en.wikipedia.org/wiki/MD5
+
+// These functions perform MD5 operations. The simplest call is MD5Sum() to
+// generate the MD5 sum of the given data.
+//
+// You can also compute the MD5 sum of data incrementally by making multiple
+// calls to MD5Update():
+//   MD5Context ctx; // intermediate MD5 data: do not use
+//   MD5Init(&ctx);
+//   MD5Update(&ctx, data1, length1);
+//   MD5Update(&ctx, data2, length2);
+//   ...
+//
+//   MD5Digest digest; // the result of the computation
+//   MD5Final(&digest, &ctx);
+//
+// You can call MD5DigestToBase16() to generate a string of the digest.
+
+// The output of an MD5 operation.
+struct MD5Digest {
+  uint8_t a[16];
+};
+
+// Used for storing intermediate data during an MD5 computation. Callers
+// should not access the data.
+typedef char MD5Context[88];
+
+// Initializes the given MD5 context structure for subsequent calls to
+// MD5Update().
+void MD5Init(MD5Context* context);
+
+// For the given buffer of |data| as a std::string_view, updates the given MD5
+// context with the sum of the data. You can call this any number of times
+// during the computation, except that MD5Init() must have been called first.
+void MD5Update(MD5Context* context, const std::string_view& data);
+
+// Finalizes the MD5 operation and fills the buffer with the digest.
+void MD5Final(MD5Digest* digest, MD5Context* context);
+
+// MD5IntermediateFinal() generates a digest without finalizing the MD5
+// operation.  Can be used to generate digests for the input seen thus far,
+// without affecting the digest generated for the entire input.
+void MD5IntermediateFinal(MD5Digest* digest, const MD5Context* context);
+
+// Converts a digest into human-readable hexadecimal.
+std::string MD5DigestToBase16(const MD5Digest& digest);
+
+// Computes the MD5 sum of the given data buffer with the given length.
+// The given 'digest' structure will be filled with the result data.
+void MD5Sum(const void* data, size_t length, MD5Digest* digest);
+
+// Returns the MD5 (in hexadecimal) of a string.
+std::string MD5String(const std::string_view& str);
+
+}  // namespace base
+
+#endif  // BASE_MD5_H_
diff --git a/src/base/memory/free_deleter.h b/src/base/memory/free_deleter.h
new file mode 100644 (file)
index 0000000..e12795c
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_FREE_DELETER_H_
+#define BASE_MEMORY_FREE_DELETER_H_
+
+#include <stdlib.h>
+
+namespace base {
+
+// Function object which invokes 'free' on its parameter, which must be
+// a pointer. Can be used to store malloc-allocated pointers in std::unique_ptr:
+//
+// std::unique_ptr<int, base::FreeDeleter> foo_ptr(
+//     static_cast<int*>(malloc(sizeof(int))));
+struct FreeDeleter {
+  inline void operator()(void* ptr) const { free(ptr); }
+};
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_FREE_DELETER_H_
diff --git a/src/base/memory/ptr_util.h b/src/base/memory/ptr_util.h
new file mode 100644 (file)
index 0000000..42f4f49
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_PTR_UTIL_H_
+#define BASE_MEMORY_PTR_UTIL_H_
+
+#include <memory>
+#include <utility>
+
+namespace base {
+
+// Helper to transfer ownership of a raw pointer to a std::unique_ptr<T>.
+// Note that std::unique_ptr<T> has very different semantics from
+// std::unique_ptr<T[]>: do not use this helper for array allocations.
+template <typename T>
+std::unique_ptr<T> WrapUnique(T* ptr) {
+  return std::unique_ptr<T>(ptr);
+}
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_PTR_UTIL_H_
diff --git a/src/base/memory/raw_scoped_refptr_mismatch_checker.h b/src/base/memory/raw_scoped_refptr_mismatch_checker.h
new file mode 100644 (file)
index 0000000..4309849
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
+#define BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
+
+#include <type_traits>
+
+#include "base/template_util.h"
+
+// It is dangerous to post a task with a T* argument where T is a subtype of
+// RefCounted(Base|ThreadSafeBase), since by the time the parameter is used, the
+// object may already have been deleted since it was not held with a
+// scoped_refptr. Example: http://crbug.com/27191
+// The following set of traits are designed to generate a compile error
+// whenever this antipattern is attempted.
+
+namespace base {
+
+// This is a base internal implementation file used by task.h and callback.h.
+// Not for public consumption, so we wrap it in namespace internal.
+namespace internal {
+
+template <typename T, typename = void>
+struct IsRefCountedType : std::false_type {};
+
+template <typename T>
+struct IsRefCountedType<T,
+                        std::void_t<decltype(std::declval<T*>()->AddRef()),
+                                    decltype(std::declval<T*>()->Release())>>
+    : std::true_type {};
+
+template <typename T>
+struct NeedsScopedRefptrButGetsRawPtr {
+  static_assert(!std::is_reference<T>::value,
+                "NeedsScopedRefptrButGetsRawPtr requires non-reference type.");
+
+  enum {
+    // Human readable translation: you needed to be a scoped_refptr if you are a
+    // raw pointer type and are convertible to a RefCounted(Base|ThreadSafeBase)
+    // type.
+    value = std::is_pointer<T>::value &&
+            IsRefCountedType<std::remove_pointer_t<T>>::value
+  };
+};
+
+}  // namespace internal
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
diff --git a/src/base/memory/ref_counted.cc b/src/base/memory/ref_counted.cc
new file mode 100644 (file)
index 0000000..79ab853
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+
+namespace base {
+
+namespace subtle {
+
+bool RefCountedThreadSafeBase::HasOneRef() const {
+  return ref_count_.IsOne();
+}
+
+#if defined(ARCH_CPU_64_BIT)
+void RefCountedBase::AddRefImpl() const {
+  // Check if |ref_count_| overflow only on 64 bit archs since the number of
+  // objects may exceed 2^32.
+  // To avoid the binary size bloat, use non-inline function here.
+  CHECK(++ref_count_ > 0);
+}
+#endif
+
+#if !defined(ARCH_CPU_X86_FAMILY)
+bool RefCountedThreadSafeBase::Release() const {
+  return ReleaseImpl();
+}
+void RefCountedThreadSafeBase::AddRef() const {
+  AddRefImpl();
+}
+#endif
+
+}  // namespace subtle
+
+}  // namespace base
diff --git a/src/base/memory/ref_counted.h b/src/base/memory/ref_counted.h
new file mode 100644 (file)
index 0000000..acfa004
--- /dev/null
@@ -0,0 +1,317 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_REF_COUNTED_H_
+#define BASE_MEMORY_REF_COUNTED_H_
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/atomic_ref_count.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "util/build_config.h"
+
+namespace base {
+namespace subtle {
+
+class RefCountedBase {
+ public:
+  bool HasOneRef() const { return ref_count_ == 1; }
+
+ protected:
+  explicit RefCountedBase(StartRefCountFromZeroTag) {}
+
+  explicit RefCountedBase(StartRefCountFromOneTag) : ref_count_(1) {}
+
+  ~RefCountedBase() {}
+
+  void AddRef() const { AddRefImpl(); }
+
+  // Returns true if the object should self-delete.
+  bool Release() const {
+    --ref_count_;
+
+    // TODO(maruel): Add back once it doesn't assert 500 times/sec.
+    // Current thread books the critical section "AddRelease"
+    // without release it.
+    // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_);
+
+    return ref_count_ == 0;
+  }
+
+  // Returns true if it is safe to read or write the object, from a thread
+  // safety standpoint. Should be DCHECK'd from the methods of RefCounted
+  // classes if there is a danger of objects being shared across threads.
+  //
+  // This produces fewer false positives than adding a separate SequenceChecker
+  // into the subclass, because it automatically detaches from the sequence when
+  // the reference count is 1 (and never fails if there is only one reference).
+  //
+  // This means unlike a separate SequenceChecker, it will permit a singly
+  // referenced object to be passed between threads (not holding a reference on
+  // the sending thread), but will trap if the sending thread holds onto a
+  // reference, or if the object is accessed from multiple threads
+  // simultaneously.
+  bool IsOnValidSequence() const { return true; }
+
+ private:
+  template <typename U>
+  friend scoped_refptr<U> base::AdoptRef(U*);
+
+  void Adopted() const {}
+
+#if defined(ARCH_CPU_64_BIT)
+  void AddRefImpl() const;
+#else
+  void AddRefImpl() const { ++ref_count_; }
+#endif
+
+  mutable uint32_t ref_count_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(RefCountedBase);
+};
+
+class RefCountedThreadSafeBase {
+ public:
+  bool HasOneRef() const;
+
+ protected:
+  explicit constexpr RefCountedThreadSafeBase(StartRefCountFromZeroTag) {}
+  explicit constexpr RefCountedThreadSafeBase(StartRefCountFromOneTag)
+      : ref_count_(1) {}
+
+  ~RefCountedThreadSafeBase() = default;
+
+// Release and AddRef are suitable for inlining on X86 because they generate
+// very small code sequences. On other platforms (ARM), it causes a size
+// regression and is probably not worth it.
+#if defined(ARCH_CPU_X86_FAMILY)
+  // Returns true if the object should self-delete.
+  bool Release() const { return ReleaseImpl(); }
+  void AddRef() const { AddRefImpl(); }
+#else
+  // Returns true if the object should self-delete.
+  bool Release() const;
+  void AddRef() const;
+#endif
+
+ private:
+  template <typename U>
+  friend scoped_refptr<U> base::AdoptRef(U*);
+
+  void Adopted() const {}
+
+  ALWAYS_INLINE void AddRefImpl() const { ref_count_.Increment(); }
+
+  ALWAYS_INLINE bool ReleaseImpl() const {
+    if (!ref_count_.Decrement()) {
+      return true;
+    }
+    return false;
+  }
+
+  mutable AtomicRefCount ref_count_{0};
+
+  DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase);
+};
+
+}  // namespace subtle
+
+// ScopedAllowCrossThreadRefCountAccess disables the check documented on
+// RefCounted below for rare pre-existing use cases where thread-safety was
+// guaranteed through other means (e.g. explicit sequencing of calls across
+// execution sequences when bouncing between threads in order). New callers
+// should refrain from using this (callsites handling thread-safety through
+// locks should use RefCountedThreadSafe per the overhead of its atomics being
+// negligible compared to locks anyways and callsites doing explicit sequencing
+// should properly std::move() the ref to avoid hitting this check).
+// TODO(tzik): Cleanup existing use cases and remove
+// ScopedAllowCrossThreadRefCountAccess.
+class ScopedAllowCrossThreadRefCountAccess final {
+ public:
+  ScopedAllowCrossThreadRefCountAccess() {}
+  ~ScopedAllowCrossThreadRefCountAccess() {}
+};
+
+//
+// A base class for reference counted classes.  Otherwise, known as a cheap
+// knock-off of WebKit's RefCounted<T> class.  To use this, just extend your
+// class from it like so:
+//
+//   class MyFoo : public base::RefCounted<MyFoo> {
+//    ...
+//    private:
+//     friend class base::RefCounted<MyFoo>;
+//     ~MyFoo();
+//   };
+//
+// You should always make your destructor non-public, to avoid any code deleting
+// the object accidentally while there are references to it.
+//
+//
+// The ref count manipulation to RefCounted is NOT thread safe and has DCHECKs
+// to trap unsafe cross thread usage. A subclass instance of RefCounted can be
+// passed to another execution sequence only when its ref count is 1. If the ref
+// count is more than 1, the RefCounted class verifies the ref updates are made
+// on the same execution sequence as the previous ones. The subclass can also
+// manually call IsOnValidSequence to trap other non-thread-safe accesses; see
+// the documentation for that method.
+//
+//
+// The reference count starts from zero by default, and we intended to migrate
+// to start-from-one ref count. Put REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE() to
+// the ref counted class to opt-in.
+//
+// If an object has start-from-one ref count, the first scoped_refptr need to be
+// created by base::AdoptRef() or base::MakeRefCounted(). We can use
+// base::MakeRefCounted() to create create both type of ref counted object.
+//
+// The motivations to use start-from-one ref count are:
+//  - Start-from-one ref count doesn't need the ref count increment for the
+//    first reference.
+//  - It can detect an invalid object acquisition for a being-deleted object
+//    that has zero ref count. That tends to happen on custom deleter that
+//    delays the deletion.
+//    TODO(tzik): Implement invalid acquisition detection.
+//  - Behavior parity to Blink's WTF::RefCounted, whose count starts from one.
+//    And start-from-one ref count is a step to merge WTF::RefCounted into
+//    base::RefCounted.
+//
+#define REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE()             \
+  static constexpr ::base::subtle::StartRefCountFromOneTag \
+      kRefCountPreference = ::base::subtle::kStartRefCountFromOneTag
+
+template <class T, typename Traits>
+class RefCounted;
+
+template <typename T>
+struct DefaultRefCountedTraits {
+  static void Destruct(const T* x) {
+    RefCounted<T, DefaultRefCountedTraits>::DeleteInternal(x);
+  }
+};
+
+template <class T, typename Traits = DefaultRefCountedTraits<T>>
+class RefCounted : public subtle::RefCountedBase {
+ public:
+  static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
+      subtle::kStartRefCountFromZeroTag;
+
+  RefCounted() : subtle::RefCountedBase(T::kRefCountPreference) {}
+
+  void AddRef() const { subtle::RefCountedBase::AddRef(); }
+
+  void Release() const {
+    if (subtle::RefCountedBase::Release()) {
+      // Prune the code paths which the static analyzer may take to simulate
+      // object destruction. Use-after-free errors aren't possible given the
+      // lifetime guarantees of the refcounting system.
+      ANALYZER_SKIP_THIS_PATH();
+
+      Traits::Destruct(static_cast<const T*>(this));
+    }
+  }
+
+ protected:
+  ~RefCounted() = default;
+
+ private:
+  friend struct DefaultRefCountedTraits<T>;
+  template <typename U>
+  static void DeleteInternal(const U* x) {
+    delete x;
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RefCounted);
+};
+
+// Forward declaration.
+template <class T, typename Traits>
+class RefCountedThreadSafe;
+
+// Default traits for RefCountedThreadSafe<T>.  Deletes the object when its ref
+// count reaches 0.  Overload to delete it on a different thread etc.
+template <typename T>
+struct DefaultRefCountedThreadSafeTraits {
+  static void Destruct(const T* x) {
+    // Delete through RefCountedThreadSafe to make child classes only need to be
+    // friend with RefCountedThreadSafe instead of this struct, which is an
+    // implementation detail.
+    RefCountedThreadSafe<T, DefaultRefCountedThreadSafeTraits>::DeleteInternal(
+        x);
+  }
+};
+
+//
+// A thread-safe variant of RefCounted<T>
+//
+//   class MyFoo : public base::RefCountedThreadSafe<MyFoo> {
+//    ...
+//   };
+//
+// If you're using the default trait, then you should add compile time
+// asserts that no one else is deleting your object.  i.e.
+//    private:
+//     friend class base::RefCountedThreadSafe<MyFoo>;
+//     ~MyFoo();
+//
+// We can use REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE() with RefCountedThreadSafe
+// too. See the comment above the RefCounted definition for details.
+template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T>>
+class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
+ public:
+  static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
+      subtle::kStartRefCountFromZeroTag;
+
+  explicit RefCountedThreadSafe()
+      : subtle::RefCountedThreadSafeBase(T::kRefCountPreference) {}
+
+  void AddRef() const { subtle::RefCountedThreadSafeBase::AddRef(); }
+
+  void Release() const {
+    if (subtle::RefCountedThreadSafeBase::Release()) {
+      ANALYZER_SKIP_THIS_PATH();
+      Traits::Destruct(static_cast<const T*>(this));
+    }
+  }
+
+ protected:
+  ~RefCountedThreadSafe() = default;
+
+ private:
+  friend struct DefaultRefCountedThreadSafeTraits<T>;
+  template <typename U>
+  static void DeleteInternal(const U* x) {
+    delete x;
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);
+};
+
+//
+// A thread-safe wrapper for some piece of data so we can place other
+// things in scoped_refptrs<>.
+//
+template <typename T>
+class RefCountedData
+    : public base::RefCountedThreadSafe<base::RefCountedData<T>> {
+ public:
+  RefCountedData() : data() {}
+  RefCountedData(const T& in_value) : data(in_value) {}
+  RefCountedData(T&& in_value) : data(std::move(in_value)) {}
+
+  T data;
+
+ private:
+  friend class base::RefCountedThreadSafe<base::RefCountedData<T>>;
+  ~RefCountedData() = default;
+};
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_REF_COUNTED_H_
diff --git a/src/base/memory/scoped_policy.h b/src/base/memory/scoped_policy.h
new file mode 100644 (file)
index 0000000..5dbf204
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_SCOPED_POLICY_H_
+#define BASE_MEMORY_SCOPED_POLICY_H_
+
+namespace base {
+namespace scoped_policy {
+
+// Defines the ownership policy for a scoped object.
+enum OwnershipPolicy {
+  // The scoped object takes ownership of an object by taking over an existing
+  // ownership claim.
+  ASSUME,
+
+  // The scoped object will retain the the object and any initial ownership is
+  // not changed.
+  RETAIN
+};
+
+}  // namespace scoped_policy
+}  // namespace base
+
+#endif  // BASE_MEMORY_SCOPED_POLICY_H_
diff --git a/src/base/memory/scoped_refptr.h b/src/base/memory/scoped_refptr.h
new file mode 100644 (file)
index 0000000..a257617
--- /dev/null
@@ -0,0 +1,333 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_SCOPED_REFPTR_H_
+#define BASE_MEMORY_SCOPED_REFPTR_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+
+template <class T>
+class scoped_refptr;
+
+namespace base {
+
+template <class, typename>
+class RefCounted;
+template <class, typename>
+class RefCountedThreadSafe;
+
+template <typename T>
+scoped_refptr<T> AdoptRef(T* t);
+
+namespace subtle {
+
+enum AdoptRefTag { kAdoptRefTag };
+enum StartRefCountFromZeroTag { kStartRefCountFromZeroTag };
+enum StartRefCountFromOneTag { kStartRefCountFromOneTag };
+
+template <typename T, typename U, typename V>
+constexpr bool IsRefCountPreferenceOverridden(const T*,
+                                              const RefCounted<U, V>*) {
+  return !std::is_same<std::decay_t<decltype(T::kRefCountPreference)>,
+                       std::decay_t<decltype(U::kRefCountPreference)>>::value;
+}
+
+template <typename T, typename U, typename V>
+constexpr bool IsRefCountPreferenceOverridden(
+    const T*,
+    const RefCountedThreadSafe<U, V>*) {
+  return !std::is_same<std::decay_t<decltype(T::kRefCountPreference)>,
+                       std::decay_t<decltype(U::kRefCountPreference)>>::value;
+}
+
+constexpr bool IsRefCountPreferenceOverridden(...) {
+  return false;
+}
+
+}  // namespace subtle
+
+// Creates a scoped_refptr from a raw pointer without incrementing the reference
+// count. Use this only for a newly created object whose reference count starts
+// from 1 instead of 0.
+template <typename T>
+scoped_refptr<T> AdoptRef(T* obj) {
+  using Tag = std::decay_t<decltype(T::kRefCountPreference)>;
+  static_assert(std::is_same<subtle::StartRefCountFromOneTag, Tag>::value,
+                "Use AdoptRef only for the reference count starts from one.");
+
+  DCHECK(obj);
+  DCHECK(obj->HasOneRef());
+  obj->Adopted();
+  return scoped_refptr<T>(obj, subtle::kAdoptRefTag);
+}
+
+namespace subtle {
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) {
+  return scoped_refptr<T>(obj);
+}
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromOneTag) {
+  return AdoptRef(obj);
+}
+
+}  // namespace subtle
+
+// Constructs an instance of T, which is a ref counted type, and wraps the
+// object into a scoped_refptr<T>.
+template <typename T, typename... Args>
+scoped_refptr<T> MakeRefCounted(Args&&... args) {
+  T* obj = new T(std::forward<Args>(args)...);
+  return subtle::AdoptRefIfNeeded(obj, T::kRefCountPreference);
+}
+
+// Takes an instance of T, which is a ref counted type, and wraps the object
+// into a scoped_refptr<T>.
+template <typename T>
+scoped_refptr<T> WrapRefCounted(T* t) {
+  return scoped_refptr<T>(t);
+}
+
+}  // namespace base
+
+//
+// A smart pointer class for reference counted objects.  Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference.  Sample usage:
+//
+//   class MyFoo : public RefCounted<MyFoo> {
+//    ...
+//    private:
+//     friend class RefCounted<MyFoo>;  // Allow destruction by RefCounted<>.
+//     ~MyFoo();                        // Destructor must be private/protected.
+//   };
+//
+//   void some_function() {
+//     scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+//     foo->Method(param);
+//     // |foo| is released when this function returns
+//   }
+//
+//   void some_other_function() {
+//     scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+//     ...
+//     foo = nullptr;  // explicitly releases |foo|
+//     ...
+//     if (foo)
+//       foo->Method(param);
+//   }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+//   {
+//     scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+//     scoped_refptr<MyFoo> b;
+//
+//     b.swap(a);
+//     // now, |b| references the MyFoo object, and |a| references nullptr.
+//   }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+//   {
+//     scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+//     scoped_refptr<MyFoo> b;
+//
+//     b = a;
+//     // now, |a| and |b| each own a reference to the same MyFoo object.
+//   }
+//
+// Also see Chromium's ownership and calling conventions:
+// https://chromium.googlesource.com/chromium/src/+/lkgr/styleguide/c++/c++.md#object-ownership-and-calling-conventions
+// Specifically:
+//   If the function (at least sometimes) takes a ref on a refcounted object,
+//   declare the param as scoped_refptr<T>. The caller can decide whether it
+//   wishes to transfer ownership (by calling std::move(t) when passing t) or
+//   retain its ref (by simply passing t directly).
+//   In other words, use scoped_refptr like you would a std::unique_ptr except
+//   in the odd case where it's required to hold on to a ref while handing one
+//   to another component (if a component merely needs to use t on the stack
+//   without keeping a ref: pass t as a raw T*).
+template <class T>
+class scoped_refptr {
+ public:
+  typedef T element_type;
+
+  constexpr scoped_refptr() = default;
+
+  // Constructs from raw pointer. constexpr if |p| is null.
+  constexpr scoped_refptr(T* p) : ptr_(p) {
+    if (ptr_)
+      AddRef(ptr_);
+  }
+
+  // Copy constructor. This is required in addition to the copy conversion
+  // constructor below.
+  scoped_refptr(const scoped_refptr& r) : scoped_refptr(r.ptr_) {}
+
+  // Copy conversion constructor.
+  template <typename U,
+            typename = typename std::enable_if<
+                std::is_convertible<U*, T*>::value>::type>
+  scoped_refptr(const scoped_refptr<U>& r) : scoped_refptr(r.ptr_) {}
+
+  // Move constructor. This is required in addition to the move conversion
+  // constructor below.
+  scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { r.ptr_ = nullptr; }
+
+  // Move conversion constructor.
+  template <typename U,
+            typename = typename std::enable_if<
+                std::is_convertible<U*, T*>::value>::type>
+  scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.ptr_) {
+    r.ptr_ = nullptr;
+  }
+
+  ~scoped_refptr() {
+    static_assert(!base::subtle::IsRefCountPreferenceOverridden(
+                      static_cast<T*>(nullptr), static_cast<T*>(nullptr)),
+                  "It's unsafe to override the ref count preference."
+                  " Please remove REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE"
+                  " from subclasses.");
+    if (ptr_)
+      Release(ptr_);
+  }
+
+  T* get() const { return ptr_; }
+
+  T& operator*() const {
+    DCHECK(ptr_);
+    return *ptr_;
+  }
+
+  T* operator->() const {
+    DCHECK(ptr_);
+    return ptr_;
+  }
+
+  scoped_refptr& operator=(T* p) { return *this = scoped_refptr(p); }
+
+  // Unified assignment operator.
+  scoped_refptr& operator=(scoped_refptr r) noexcept {
+    swap(r);
+    return *this;
+  }
+
+  void swap(scoped_refptr& r) noexcept { std::swap(ptr_, r.ptr_); }
+
+  explicit operator bool() const { return ptr_ != nullptr; }
+
+  template <typename U>
+  bool operator==(const scoped_refptr<U>& rhs) const {
+    return ptr_ == rhs.get();
+  }
+
+  template <typename U>
+  bool operator!=(const scoped_refptr<U>& rhs) const {
+    return !operator==(rhs);
+  }
+
+  template <typename U>
+  bool operator<(const scoped_refptr<U>& rhs) const {
+    return ptr_ < rhs.get();
+  }
+
+ protected:
+  T* ptr_ = nullptr;
+
+ private:
+  template <typename U>
+  friend scoped_refptr<U> base::AdoptRef(U*);
+
+  scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {}
+
+  // Friend required for move constructors that set r.ptr_ to null.
+  template <typename U>
+  friend class scoped_refptr;
+
+  // Non-inline helpers to allow:
+  //     class Opaque;
+  //     extern template class scoped_refptr<Opaque>;
+  // Otherwise the compiler will complain that Opaque is an incomplete type.
+  static void AddRef(T* ptr);
+  static void Release(T* ptr);
+};
+
+// static
+template <typename T>
+void scoped_refptr<T>::AddRef(T* ptr) {
+  ptr->AddRef();
+}
+
+// static
+template <typename T>
+void scoped_refptr<T>::Release(T* ptr) {
+  ptr->Release();
+}
+
+template <typename T, typename U>
+bool operator==(const scoped_refptr<T>& lhs, const U* rhs) {
+  return lhs.get() == rhs;
+}
+
+template <typename T, typename U>
+bool operator==(const T* lhs, const scoped_refptr<U>& rhs) {
+  return lhs == rhs.get();
+}
+
+template <typename T>
+bool operator==(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+  return !static_cast<bool>(lhs);
+}
+
+template <typename T>
+bool operator==(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+  return !static_cast<bool>(rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const scoped_refptr<T>& lhs, const U* rhs) {
+  return !operator==(lhs, rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const T* lhs, const scoped_refptr<U>& rhs) {
+  return !operator==(lhs, rhs);
+}
+
+template <typename T>
+bool operator!=(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+  return !operator==(lhs, null);
+}
+
+template <typename T>
+bool operator!=(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+  return !operator==(null, rhs);
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& out, const scoped_refptr<T>& p) {
+  return out << p.get();
+}
+
+template <typename T>
+void swap(scoped_refptr<T>& lhs, scoped_refptr<T>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+#endif  // BASE_MEMORY_SCOPED_REFPTR_H_
diff --git a/src/base/memory/weak_ptr.cc b/src/base/memory/weak_ptr.cc
new file mode 100644 (file)
index 0000000..4abeb7f
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/weak_ptr.h"
+
+namespace base {
+namespace internal {
+
+WeakReference::Flag::Flag() : is_valid_(true) {}
+
+void WeakReference::Flag::Invalidate() {
+  is_valid_ = false;
+}
+
+bool WeakReference::Flag::IsValid() const {
+  return is_valid_;
+}
+
+WeakReference::Flag::~Flag() = default;
+
+WeakReference::WeakReference() = default;
+
+WeakReference::WeakReference(const scoped_refptr<Flag>& flag) : flag_(flag) {}
+
+WeakReference::~WeakReference() = default;
+
+WeakReference::WeakReference(WeakReference&& other) = default;
+
+WeakReference::WeakReference(const WeakReference& other) = default;
+
+bool WeakReference::is_valid() const {
+  return flag_ && flag_->IsValid();
+}
+
+WeakReferenceOwner::WeakReferenceOwner() = default;
+
+WeakReferenceOwner::~WeakReferenceOwner() {
+  Invalidate();
+}
+
+WeakReference WeakReferenceOwner::GetRef() const {
+  // If we hold the last reference to the Flag then create a new one.
+  if (!HasRefs())
+    flag_ = new WeakReference::Flag();
+
+  return WeakReference(flag_);
+}
+
+void WeakReferenceOwner::Invalidate() {
+  if (flag_) {
+    flag_->Invalidate();
+    flag_ = nullptr;
+  }
+}
+
+WeakPtrBase::WeakPtrBase() : ptr_(0) {}
+
+WeakPtrBase::~WeakPtrBase() = default;
+
+WeakPtrBase::WeakPtrBase(const WeakReference& ref, uintptr_t ptr)
+    : ref_(ref), ptr_(ptr) {
+  DCHECK(ptr_);
+}
+
+WeakPtrFactoryBase::WeakPtrFactoryBase(uintptr_t ptr) : ptr_(ptr) {
+  DCHECK(ptr_);
+}
+
+WeakPtrFactoryBase::~WeakPtrFactoryBase() {
+  ptr_ = 0;
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/src/base/memory/weak_ptr.h b/src/base/memory/weak_ptr.h
new file mode 100644 (file)
index 0000000..3b2a0af
--- /dev/null
@@ -0,0 +1,378 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Weak pointers are pointers to an object that do not affect its lifetime,
+// and which may be invalidated (i.e. reset to nullptr) by the object, or its
+// owner, at any time, most commonly when the object is about to be deleted.
+
+// Weak pointers are useful when an object needs to be accessed safely by one
+// or more objects other than its owner, and those callers can cope with the
+// object vanishing and e.g. tasks posted to it being silently dropped.
+// Reference-counting such an object would complicate the ownership graph and
+// make it harder to reason about the object's lifetime.
+
+// EXAMPLE:
+//
+//  class Controller {
+//   public:
+//    Controller() : weak_factory_(this) {}
+//    void SpawnWorker() { Worker::StartNew(weak_factory_.GetWeakPtr()); }
+//    void WorkComplete(const Result& result) { ... }
+//   private:
+//    // Member variables should appear before the WeakPtrFactory, to ensure
+//    // that any WeakPtrs to Controller are invalidated before its members
+//    // variable's destructors are executed, rendering them invalid.
+//    WeakPtrFactory<Controller> weak_factory_;
+//  };
+//
+//  class Worker {
+//   public:
+//    static void StartNew(const WeakPtr<Controller>& controller) {
+//      Worker* worker = new Worker(controller);
+//      // Kick off asynchronous processing...
+//    }
+//   private:
+//    Worker(const WeakPtr<Controller>& controller)
+//        : controller_(controller) {}
+//    void DidCompleteAsynchronousProcessing(const Result& result) {
+//      if (controller_)
+//        controller_->WorkComplete(result);
+//    }
+//    WeakPtr<Controller> controller_;
+//  };
+//
+// With this implementation a caller may use SpawnWorker() to dispatch multiple
+// Workers and subsequently delete the Controller, without waiting for all
+// Workers to have completed.
+
+// ------------------------- IMPORTANT: Thread-safety -------------------------
+
+// Weak pointers may be passed safely between threads, but must always be
+// dereferenced and invalidated on the same SequencedTaskRunner otherwise
+// checking the pointer would be racey.
+//
+// To ensure correct use, the first time a WeakPtr issued by a WeakPtrFactory
+// is dereferenced, the factory and its WeakPtrs become bound to the calling
+// thread or current SequencedWorkerPool token, and cannot be dereferenced or
+// invalidated on any other task runner. Bound WeakPtrs can still be handed
+// off to other task runners, e.g. to use to post tasks back to object on the
+// bound sequence.
+//
+// If all WeakPtr objects are destroyed or invalidated then the factory is
+// unbound from the SequencedTaskRunner/Thread. The WeakPtrFactory may then be
+// destroyed, or new WeakPtr objects may be used, from a different sequence.
+//
+// Thus, at least one WeakPtr object must exist and have been dereferenced on
+// the correct thread to enforce that other WeakPtr objects will enforce they
+// are used on the desired thread.
+
+#ifndef BASE_MEMORY_WEAK_PTR_H_
+#define BASE_MEMORY_WEAK_PTR_H_
+
+#include <cstddef>
+#include <type_traits>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+
+template <typename T>
+class SupportsWeakPtr;
+template <typename T>
+class WeakPtr;
+
+namespace internal {
+// These classes are part of the WeakPtr implementation.
+// DO NOT USE THESE CLASSES DIRECTLY YOURSELF.
+
+class WeakReference {
+ public:
+  // Although Flag is bound to a specific SequencedTaskRunner, it may be
+  // deleted from another via base::WeakPtr::~WeakPtr().
+  class Flag : public RefCountedThreadSafe<Flag> {
+   public:
+    Flag();
+
+    void Invalidate();
+    bool IsValid() const;
+
+   private:
+    friend class base::RefCountedThreadSafe<Flag>;
+
+    ~Flag();
+
+    bool is_valid_;
+  };
+
+  WeakReference();
+  explicit WeakReference(const scoped_refptr<Flag>& flag);
+  ~WeakReference();
+
+  WeakReference(WeakReference&& other);
+  WeakReference(const WeakReference& other);
+  WeakReference& operator=(WeakReference&& other) = default;
+  WeakReference& operator=(const WeakReference& other) = default;
+
+  bool is_valid() const;
+
+ private:
+  scoped_refptr<const Flag> flag_;
+};
+
+class WeakReferenceOwner {
+ public:
+  WeakReferenceOwner();
+  ~WeakReferenceOwner();
+
+  WeakReference GetRef() const;
+
+  bool HasRefs() const { return flag_ && !flag_->HasOneRef(); }
+
+  void Invalidate();
+
+ private:
+  mutable scoped_refptr<WeakReference::Flag> flag_;
+};
+
+// This class simplifies the implementation of WeakPtr's type conversion
+// constructor by avoiding the need for a public accessor for ref_.  A
+// WeakPtr<T> cannot access the private members of WeakPtr<U>, so this
+// base class gives us a way to access ref_ in a protected fashion.
+class WeakPtrBase {
+ public:
+  WeakPtrBase();
+  ~WeakPtrBase();
+
+  WeakPtrBase(const WeakPtrBase& other) = default;
+  WeakPtrBase(WeakPtrBase&& other) = default;
+  WeakPtrBase& operator=(const WeakPtrBase& other) = default;
+  WeakPtrBase& operator=(WeakPtrBase&& other) = default;
+
+  void reset() {
+    ref_ = internal::WeakReference();
+    ptr_ = 0;
+  }
+
+ protected:
+  WeakPtrBase(const WeakReference& ref, uintptr_t ptr);
+
+  WeakReference ref_;
+
+  // This pointer is only valid when ref_.is_valid() is true.  Otherwise, its
+  // value is undefined (as opposed to nullptr).
+  uintptr_t ptr_;
+};
+
+// This class provides a common implementation of common functions that would
+// otherwise get instantiated separately for each distinct instantiation of
+// SupportsWeakPtr<>.
+class SupportsWeakPtrBase {
+ public:
+  // A safe static downcast of a WeakPtr<Base> to WeakPtr<Derived>. This
+  // conversion will only compile if there is exists a Base which inherits
+  // from SupportsWeakPtr<Base>. See base::AsWeakPtr() below for a helper
+  // function that makes calling this easier.
+  //
+  // Precondition: t != nullptr
+  template <typename Derived>
+  static WeakPtr<Derived> StaticAsWeakPtr(Derived* t) {
+    static_assert(
+        std::is_base_of<internal::SupportsWeakPtrBase, Derived>::value,
+        "AsWeakPtr argument must inherit from SupportsWeakPtr");
+    return AsWeakPtrImpl<Derived>(t);
+  }
+
+ private:
+  // This template function uses type inference to find a Base of Derived
+  // which is an instance of SupportsWeakPtr<Base>. We can then safely
+  // static_cast the Base* to a Derived*.
+  template <typename Derived, typename Base>
+  static WeakPtr<Derived> AsWeakPtrImpl(SupportsWeakPtr<Base>* t) {
+    WeakPtr<Base> ptr = t->AsWeakPtr();
+    return WeakPtr<Derived>(
+        ptr.ref_, static_cast<Derived*>(reinterpret_cast<Base*>(ptr.ptr_)));
+  }
+};
+
+}  // namespace internal
+
+template <typename T>
+class WeakPtrFactory;
+
+// The WeakPtr class holds a weak reference to |T*|.
+//
+// This class is designed to be used like a normal pointer.  You should always
+// null-test an object of this class before using it or invoking a method that
+// may result in the underlying object being destroyed.
+//
+// EXAMPLE:
+//
+//   class Foo { ... };
+//   WeakPtr<Foo> foo;
+//   if (foo)
+//     foo->method();
+//
+template <typename T>
+class WeakPtr : public internal::WeakPtrBase {
+ public:
+  WeakPtr() = default;
+
+  WeakPtr(std::nullptr_t) {}
+
+  // Allow conversion from U to T provided U "is a" T. Note that this
+  // is separate from the (implicit) copy and move constructors.
+  template <typename U>
+  WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other) {
+    // Need to cast from U* to T* to do pointer adjustment in case of multiple
+    // inheritance. This also enforces the "U is a T" rule.
+    T* t = reinterpret_cast<U*>(other.ptr_);
+    ptr_ = reinterpret_cast<uintptr_t>(t);
+  }
+  template <typename U>
+  WeakPtr(WeakPtr<U>&& other) : WeakPtrBase(std::move(other)) {
+    // Need to cast from U* to T* to do pointer adjustment in case of multiple
+    // inheritance. This also enforces the "U is a T" rule.
+    T* t = reinterpret_cast<U*>(other.ptr_);
+    ptr_ = reinterpret_cast<uintptr_t>(t);
+  }
+
+  T* get() const {
+    return ref_.is_valid() ? reinterpret_cast<T*>(ptr_) : nullptr;
+  }
+
+  T& operator*() const {
+    DCHECK(get() != nullptr);
+    return *get();
+  }
+  T* operator->() const {
+    DCHECK(get() != nullptr);
+    return get();
+  }
+
+  // Allow conditionals to test validity, e.g. if (weak_ptr) {...};
+  explicit operator bool() const { return get() != nullptr; }
+
+ private:
+  friend class internal::SupportsWeakPtrBase;
+  template <typename U>
+  friend class WeakPtr;
+  friend class SupportsWeakPtr<T>;
+  friend class WeakPtrFactory<T>;
+
+  WeakPtr(const internal::WeakReference& ref, T* ptr)
+      : WeakPtrBase(ref, reinterpret_cast<uintptr_t>(ptr)) {}
+};
+
+// Allow callers to compare WeakPtrs against nullptr to test validity.
+template <class T>
+bool operator!=(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
+  return !(weak_ptr == nullptr);
+}
+template <class T>
+bool operator!=(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
+  return weak_ptr != nullptr;
+}
+template <class T>
+bool operator==(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
+  return weak_ptr.get() == nullptr;
+}
+template <class T>
+bool operator==(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
+  return weak_ptr == nullptr;
+}
+
+namespace internal {
+class WeakPtrFactoryBase {
+ protected:
+  WeakPtrFactoryBase(uintptr_t ptr);
+  ~WeakPtrFactoryBase();
+  internal::WeakReferenceOwner weak_reference_owner_;
+  uintptr_t ptr_;
+};
+}  // namespace internal
+
+// A class may be composed of a WeakPtrFactory and thereby
+// control how it exposes weak pointers to itself.  This is helpful if you only
+// need weak pointers within the implementation of a class.  This class is also
+// useful when working with primitive types.  For example, you could have a
+// WeakPtrFactory<bool> that is used to pass around a weak reference to a bool.
+template <class T>
+class WeakPtrFactory : public internal::WeakPtrFactoryBase {
+ public:
+  explicit WeakPtrFactory(T* ptr)
+      : WeakPtrFactoryBase(reinterpret_cast<uintptr_t>(ptr)) {}
+
+  ~WeakPtrFactory() = default;
+
+  WeakPtr<T> GetWeakPtr() {
+    return WeakPtr<T>(weak_reference_owner_.GetRef(),
+                      reinterpret_cast<T*>(ptr_));
+  }
+
+  // Call this method to invalidate all existing weak pointers.
+  void InvalidateWeakPtrs() {
+    DCHECK(ptr_);
+    weak_reference_owner_.Invalidate();
+  }
+
+  // Call this method to determine if any weak pointers exist.
+  bool HasWeakPtrs() const {
+    DCHECK(ptr_);
+    return weak_reference_owner_.HasRefs();
+  }
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);
+};
+
+// A class may extend from SupportsWeakPtr to let others take weak pointers to
+// it. This avoids the class itself implementing boilerplate to dispense weak
+// pointers.  However, since SupportsWeakPtr's destructor won't invalidate
+// weak pointers to the class until after the derived class' members have been
+// destroyed, its use can lead to subtle use-after-destroy issues.
+template <class T>
+class SupportsWeakPtr : public internal::SupportsWeakPtrBase {
+ public:
+  SupportsWeakPtr() = default;
+
+  WeakPtr<T> AsWeakPtr() {
+    return WeakPtr<T>(weak_reference_owner_.GetRef(), static_cast<T*>(this));
+  }
+
+ protected:
+  ~SupportsWeakPtr() = default;
+
+ private:
+  internal::WeakReferenceOwner weak_reference_owner_;
+  DISALLOW_COPY_AND_ASSIGN(SupportsWeakPtr);
+};
+
+// Helper function that uses type deduction to safely return a WeakPtr<Derived>
+// when Derived doesn't directly extend SupportsWeakPtr<Derived>, instead it
+// extends a Base that extends SupportsWeakPtr<Base>.
+//
+// EXAMPLE:
+//   class Base : public base::SupportsWeakPtr<Producer> {};
+//   class Derived : public Base {};
+//
+//   Derived derived;
+//   base::WeakPtr<Derived> ptr = base::AsWeakPtr(&derived);
+//
+// Note that the following doesn't work (invalid type conversion) since
+// Derived::AsWeakPtr() is WeakPtr<Base> SupportsWeakPtr<Base>::AsWeakPtr(),
+// and there's no way to safely cast WeakPtr<Base> to WeakPtr<Derived> at
+// the caller.
+//
+//   base::WeakPtr<Derived> ptr = derived.AsWeakPtr();  // Fails.
+
+template <typename Derived>
+WeakPtr<Derived> AsWeakPtr(Derived* t) {
+  return internal::SupportsWeakPtrBase::StaticAsWeakPtr<Derived>(t);
+}
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_WEAK_PTR_H_
diff --git a/src/base/numerics/checked_math.h b/src/base/numerics/checked_math.h
new file mode 100644 (file)
index 0000000..433860c
--- /dev/null
@@ -0,0 +1,393 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_CHECKED_MATH_H_
+#define BASE_NUMERICS_CHECKED_MATH_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/checked_math_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+class CheckedNumeric {
+  static_assert(std::is_arithmetic<T>::value,
+                "CheckedNumeric<T>: T must be a numeric type.");
+
+ public:
+  using type = T;
+
+  constexpr CheckedNumeric() = default;
+
+  // Copy constructor.
+  template <typename Src>
+  constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
+      : state_(rhs.state_.value(), rhs.IsValid()) {}
+
+  template <typename Src>
+  friend class CheckedNumeric;
+
+  // This is not an explicit constructor because we implicitly upgrade regular
+  // numerics to CheckedNumerics to make them easier to use.
+  template <typename Src>
+  constexpr CheckedNumeric(Src value)  // NOLINT(runtime/explicit)
+      : state_(value) {
+    static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+  }
+
+  // This is not an explicit constructor because we want a seamless conversion
+  // from StrictNumeric types.
+  template <typename Src>
+  constexpr CheckedNumeric(
+      StrictNumeric<Src> value)  // NOLINT(runtime/explicit)
+      : state_(static_cast<Src>(value)) {}
+
+  // IsValid() - The public API to test if a CheckedNumeric is currently valid.
+  // A range checked destination type can be supplied using the Dst template
+  // parameter.
+  template <typename Dst = T>
+  constexpr bool IsValid() const {
+    return state_.is_valid() &&
+           IsValueInRangeForNumericType<Dst>(state_.value());
+  }
+
+  // AssignIfValid(Dst) - Assigns the underlying value if it is currently valid
+  // and is within the range supported by the destination type. Returns true if
+  // successful and false otherwise.
+  template <typename Dst>
+#if defined(__clang__) || defined(__GNUC__)
+  __attribute__((warn_unused_result))
+#elif defined(_MSC_VER)
+  _Check_return_
+#endif
+  constexpr bool
+  AssignIfValid(Dst* result) const {
+    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+               ? ((*result = static_cast<Dst>(state_.value())), true)
+               : false;
+  }
+
+  // ValueOrDie() - The primary accessor for the underlying value. If the
+  // current state is not valid it will CHECK and crash.
+  // A range checked destination type can be supplied using the Dst template
+  // parameter, which will trigger a CHECK if the value is not in bounds for
+  // the destination.
+  // The CHECK behavior can be overridden by supplying a handler as a
+  // template parameter, for test code, etc. However, the handler cannot access
+  // the underlying value, and it is not available through other means.
+  template <typename Dst = T, class CheckHandler = CheckOnFailure>
+  constexpr StrictNumeric<Dst> ValueOrDie() const {
+    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+               ? static_cast<Dst>(state_.value())
+               : CheckHandler::template HandleFailure<Dst>();
+  }
+
+  // ValueOrDefault(T default_value) - A convenience method that returns the
+  // current value if the state is valid, and the supplied default_value for
+  // any other state.
+  // A range checked destination type can be supplied using the Dst template
+  // parameter. WARNING: This function may fail to compile or CHECK at runtime
+  // if the supplied default_value is not within range of the destination type.
+  template <typename Dst = T, typename Src>
+  constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
+    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+               ? static_cast<Dst>(state_.value())
+               : checked_cast<Dst>(default_value);
+  }
+
+  // Returns a checked numeric of the specified type, cast from the current
+  // CheckedNumeric. If the current state is invalid or the destination cannot
+  // represent the result then the returned CheckedNumeric will be invalid.
+  template <typename Dst>
+  constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+    return *this;
+  }
+
+  // This friend method is available solely for providing more detailed logging
+  // in the the tests. Do not implement it in production code, because the
+  // underlying values may change at any time.
+  template <typename U>
+  friend U GetNumericValueForTest(const CheckedNumeric<U>& src);
+
+  // Prototypes for the supported arithmetic operator overloads.
+  template <typename Src>
+  constexpr CheckedNumeric& operator+=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator-=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator*=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator/=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator%=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator<<=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator>>=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator&=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator|=(const Src rhs);
+  template <typename Src>
+  constexpr CheckedNumeric& operator^=(const Src rhs);
+
+  constexpr CheckedNumeric operator-() const {
+    // The negation of two's complement int min is int min, so we simply
+    // check for that in the constexpr case.
+    // We use an optimized code path for a known run-time variable.
+    return MustTreatAsConstexpr(state_.value()) || !std::is_signed<T>::value ||
+                   std::is_floating_point<T>::value
+               ? CheckedNumeric<T>(
+                     NegateWrapper(state_.value()),
+                     IsValid() && (!std::is_signed<T>::value ||
+                                   std::is_floating_point<T>::value ||
+                                   NegateWrapper(state_.value()) !=
+                                       std::numeric_limits<T>::lowest()))
+               : FastRuntimeNegate();
+  }
+
+  constexpr CheckedNumeric operator~() const {
+    return CheckedNumeric<decltype(InvertWrapper(T()))>(
+        InvertWrapper(state_.value()), IsValid());
+  }
+
+  constexpr CheckedNumeric Abs() const {
+    return !IsValueNegative(state_.value()) ? *this : -*this;
+  }
+
+  template <typename U>
+  constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
+      const U rhs) const {
+    using R = typename UnderlyingType<U>::type;
+    using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
+    // TODO(jschuh): This can be converted to the MathOp version and remain
+    // constexpr once we have C++14 support.
+    return CheckedNumeric<result_type>(
+        static_cast<result_type>(
+            IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+                ? state_.value()
+                : Wrapper<U>::value(rhs)),
+        state_.is_valid() && Wrapper<U>::is_valid(rhs));
+  }
+
+  template <typename U>
+  constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
+      const U rhs) const {
+    using R = typename UnderlyingType<U>::type;
+    using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
+    // TODO(jschuh): This can be converted to the MathOp version and remain
+    // constexpr once we have C++14 support.
+    return CheckedNumeric<result_type>(
+        static_cast<result_type>(
+            IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+                ? state_.value()
+                : Wrapper<U>::value(rhs)),
+        state_.is_valid() && Wrapper<U>::is_valid(rhs));
+  }
+
+  // This function is available only for integral types. It returns an unsigned
+  // integer of the same width as the source type, containing the absolute value
+  // of the source, and properly handling signed min.
+  constexpr CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>
+  UnsignedAbs() const {
+    return CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>(
+        SafeUnsignedAbs(state_.value()), state_.is_valid());
+  }
+
+  constexpr CheckedNumeric& operator++() {
+    *this += 1;
+    return *this;
+  }
+
+  constexpr CheckedNumeric operator++(int) {
+    CheckedNumeric value = *this;
+    *this += 1;
+    return value;
+  }
+
+  constexpr CheckedNumeric& operator--() {
+    *this -= 1;
+    return *this;
+  }
+
+  constexpr CheckedNumeric operator--(int) {
+    CheckedNumeric value = *this;
+    *this -= 1;
+    return value;
+  }
+
+  // These perform the actual math operations on the CheckedNumerics.
+  // Binary arithmetic operations.
+  template <template <typename, typename, typename> class M,
+            typename L,
+            typename R>
+  static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
+    using Math = typename MathWrapper<M, L, R>::math;
+    T result = 0;
+    bool is_valid =
+        Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
+        Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
+    return CheckedNumeric<T>(result, is_valid);
+  }
+
+  // Assignment arithmetic operations.
+  template <template <typename, typename, typename> class M, typename R>
+  constexpr CheckedNumeric& MathOp(const R rhs) {
+    using Math = typename MathWrapper<M, T, R>::math;
+    T result = 0;  // Using T as the destination saves a range check.
+    bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
+                    Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
+    *this = CheckedNumeric<T>(result, is_valid);
+    return *this;
+  }
+
+ private:
+  CheckedNumericState<T> state_;
+
+  CheckedNumeric FastRuntimeNegate() const {
+    T result;
+    bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result);
+    return CheckedNumeric<T>(result, IsValid() && success);
+  }
+
+  template <typename Src>
+  constexpr CheckedNumeric(Src value, bool is_valid)
+      : state_(value, is_valid) {}
+
+  // These wrappers allow us to handle state the same way for both
+  // CheckedNumeric and POD arithmetic types.
+  template <typename Src>
+  struct Wrapper {
+    static constexpr bool is_valid(Src) { return true; }
+    static constexpr Src value(Src value) { return value; }
+  };
+
+  template <typename Src>
+  struct Wrapper<CheckedNumeric<Src>> {
+    static constexpr bool is_valid(const CheckedNumeric<Src> v) {
+      return v.IsValid();
+    }
+    static constexpr Src value(const CheckedNumeric<Src> v) {
+      return v.state_.value();
+    }
+  };
+
+  template <typename Src>
+  struct Wrapper<StrictNumeric<Src>> {
+    static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
+    static constexpr Src value(const StrictNumeric<Src> v) {
+      return static_cast<Src>(v);
+    }
+  };
+};
+
+// Convenience functions to avoid the ugly template disambiguator syntax.
+template <typename Dst, typename Src>
+constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
+  return value.template IsValid<Dst>();
+}
+
+template <typename Dst, typename Src>
+constexpr StrictNumeric<Dst> ValueOrDieForType(
+    const CheckedNumeric<Src> value) {
+  return value.template ValueOrDie<Dst>();
+}
+
+template <typename Dst, typename Src, typename Default>
+constexpr StrictNumeric<Dst> ValueOrDefaultForType(
+    const CheckedNumeric<Src> value,
+    const Default default_value) {
+  return value.template ValueOrDefault<Dst>(default_value);
+}
+
+// Convience wrapper to return a new CheckedNumeric from the provided arithmetic
+// or CheckedNumericType.
+template <typename T>
+constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
+    const T value) {
+  return value;
+}
+
+// These implement the variadic wrapper for the math operations.
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R>
+constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
+    const L lhs,
+    const R rhs) {
+  using Math = typename MathWrapper<M, L, R>::math;
+  return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
+                                                                        rhs);
+}
+
+// General purpose wrapper template for arithmetic operations.
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R,
+          typename... Args>
+constexpr CheckedNumeric<typename ResultType<M, L, R, Args...>::type>
+CheckMathOp(const L lhs, const R rhs, const Args... args) {
+  return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
+}
+
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Add, +, +=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Sub, -, -=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mul, *, *=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Div, /, /=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mod, %, %=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Lsh, <<, <<=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Rsh, >>, >>=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, And, &, &=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Or, |, |=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Xor, ^, ^=)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Max)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min)
+
+// These are some extra StrictNumeric operators to support simple pointer
+// arithmetic with our result types. Since wrapping on a pointer is always
+// bad, we trigger the CHECK condition here.
+template <typename L, typename R>
+L* operator+(L* lhs, const StrictNumeric<R> rhs) {
+  uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
+                              CheckMul(sizeof(L), static_cast<R>(rhs)))
+                         .template ValueOrDie<uintptr_t>();
+  return reinterpret_cast<L*>(result);
+}
+
+template <typename L, typename R>
+L* operator-(L* lhs, const StrictNumeric<R> rhs) {
+  uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
+                              CheckMul(sizeof(L), static_cast<R>(rhs)))
+                         .template ValueOrDie<uintptr_t>();
+  return reinterpret_cast<L*>(result);
+}
+
+}  // namespace internal
+
+using internal::CheckAdd;
+using internal::CheckAnd;
+using internal::CheckDiv;
+using internal::CheckedNumeric;
+using internal::CheckLsh;
+using internal::CheckMax;
+using internal::CheckMin;
+using internal::CheckMod;
+using internal::CheckMul;
+using internal::CheckOr;
+using internal::CheckRsh;
+using internal::CheckSub;
+using internal::CheckXor;
+using internal::IsValidForType;
+using internal::MakeCheckedNum;
+using internal::ValueOrDefaultForType;
+using internal::ValueOrDieForType;
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_CHECKED_MATH_H_
diff --git a/src/base/numerics/checked_math_impl.h b/src/base/numerics/checked_math_impl.h
new file mode 100644 (file)
index 0000000..673f6d6
--- /dev/null
@@ -0,0 +1,567 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_CHECKED_MATH_IMPL_H_
+#define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+constexpr bool CheckedAddImpl(T x, T y, T* result) {
+  static_assert(std::is_integral<T>::value, "Type must be integral");
+  // Since the value of x+y is undefined if we have a signed type, we compute
+  // it using the unsigned type of the same size.
+  using UnsignedDst = typename std::make_unsigned<T>::type;
+  using SignedDst = typename std::make_signed<T>::type;
+  UnsignedDst ux = static_cast<UnsignedDst>(x);
+  UnsignedDst uy = static_cast<UnsignedDst>(y);
+  UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
+  *result = static_cast<T>(uresult);
+  // Addition is valid if the sign of (x + y) is equal to either that of x or
+  // that of y.
+  return (std::is_signed<T>::value)
+             ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
+             : uresult >= uy;  // Unsigned is either valid or underflow.
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedAddOp {};
+
+template <typename T, typename U>
+struct CheckedAddOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    // TODO(jschuh) Make this "constexpr if" once we're C++17.
+    if (CheckedAddFastOp<T, U>::is_supported)
+      return CheckedAddFastOp<T, U>::Do(x, y, result);
+
+    // Double the underlying type up to a full machine word.
+    using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using Promotion =
+        typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
+                                   IntegerBitsPlusSign<intptr_t>::value),
+                                  typename BigEnoughPromotion<T, U>::type,
+                                  FastPromotion>::type;
+    // Fail if either operand is out of range for the promoted type.
+    // TODO(jschuh): This could be made to work for a broader range of values.
+    if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
+                               !IsValueInRangeForNumericType<Promotion>(y))) {
+      return false;
+    }
+
+    Promotion presult = {};
+    bool is_valid = true;
+    if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+      presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
+    } else {
+      is_valid = CheckedAddImpl(static_cast<Promotion>(x),
+                                static_cast<Promotion>(y), &presult);
+    }
+    *result = static_cast<V>(presult);
+    return is_valid && IsValueInRangeForNumericType<V>(presult);
+  }
+};
+
+template <typename T>
+constexpr bool CheckedSubImpl(T x, T y, T* result) {
+  static_assert(std::is_integral<T>::value, "Type must be integral");
+  // Since the value of x+y is undefined if we have a signed type, we compute
+  // it using the unsigned type of the same size.
+  using UnsignedDst = typename std::make_unsigned<T>::type;
+  using SignedDst = typename std::make_signed<T>::type;
+  UnsignedDst ux = static_cast<UnsignedDst>(x);
+  UnsignedDst uy = static_cast<UnsignedDst>(y);
+  UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
+  *result = static_cast<T>(uresult);
+  // Subtraction is valid if either x and y have same sign, or (x-y) and x have
+  // the same sign.
+  return (std::is_signed<T>::value)
+             ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
+             : x >= y;
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedSubOp {};
+
+template <typename T, typename U>
+struct CheckedSubOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    // TODO(jschuh) Make this "constexpr if" once we're C++17.
+    if (CheckedSubFastOp<T, U>::is_supported)
+      return CheckedSubFastOp<T, U>::Do(x, y, result);
+
+    // Double the underlying type up to a full machine word.
+    using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using Promotion =
+        typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
+                                   IntegerBitsPlusSign<intptr_t>::value),
+                                  typename BigEnoughPromotion<T, U>::type,
+                                  FastPromotion>::type;
+    // Fail if either operand is out of range for the promoted type.
+    // TODO(jschuh): This could be made to work for a broader range of values.
+    if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
+                               !IsValueInRangeForNumericType<Promotion>(y))) {
+      return false;
+    }
+
+    Promotion presult = {};
+    bool is_valid = true;
+    if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+      presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
+    } else {
+      is_valid = CheckedSubImpl(static_cast<Promotion>(x),
+                                static_cast<Promotion>(y), &presult);
+    }
+    *result = static_cast<V>(presult);
+    return is_valid && IsValueInRangeForNumericType<V>(presult);
+  }
+};
+
+template <typename T>
+constexpr bool CheckedMulImpl(T x, T y, T* result) {
+  static_assert(std::is_integral<T>::value, "Type must be integral");
+  // Since the value of x*y is potentially undefined if we have a signed type,
+  // we compute it using the unsigned type of the same size.
+  using UnsignedDst = typename std::make_unsigned<T>::type;
+  using SignedDst = typename std::make_signed<T>::type;
+  const UnsignedDst ux = SafeUnsignedAbs(x);
+  const UnsignedDst uy = SafeUnsignedAbs(y);
+  UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
+  const bool is_negative =
+      std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
+  *result = is_negative ? 0 - uresult : uresult;
+  // We have a fast out for unsigned identity or zero on the second operand.
+  // After that it's an unsigned overflow check on the absolute value, with
+  // a +1 bound for a negative result.
+  return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
+         ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedMulOp {};
+
+template <typename T, typename U>
+struct CheckedMulOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    // TODO(jschuh) Make this "constexpr if" once we're C++17.
+    if (CheckedMulFastOp<T, U>::is_supported)
+      return CheckedMulFastOp<T, U>::Do(x, y, result);
+
+    using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    // Verify the destination type can hold the result (always true for 0).
+    if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
+                                !IsValueInRangeForNumericType<Promotion>(y)) &&
+                               x && y)) {
+      return false;
+    }
+
+    Promotion presult = {};
+    bool is_valid = true;
+    if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
+      // The fast op may be available with the promoted type.
+      is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(x, y, &presult);
+    } else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+      presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
+    } else {
+      is_valid = CheckedMulImpl(static_cast<Promotion>(x),
+                                static_cast<Promotion>(y), &presult);
+    }
+    *result = static_cast<V>(presult);
+    return is_valid && IsValueInRangeForNumericType<V>(presult);
+  }
+};
+
+// Division just requires a check for a zero denominator or an invalid negation
+// on signed min/-1.
+template <typename T, typename U, class Enable = void>
+struct CheckedDivOp {};
+
+template <typename T, typename U>
+struct CheckedDivOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    if (BASE_NUMERICS_UNLIKELY(!y))
+      return false;
+
+    // The overflow check can be compiled away if we don't have the exact
+    // combination of types needed to trigger this case.
+    using Promotion = typename BigEnoughPromotion<T, U>::type;
+    if (BASE_NUMERICS_UNLIKELY(
+            (std::is_signed<T>::value && std::is_signed<U>::value &&
+             IsTypeInRangeForNumericType<T, Promotion>::value &&
+             static_cast<Promotion>(x) ==
+                 std::numeric_limits<Promotion>::lowest() &&
+             y == static_cast<U>(-1)))) {
+      return false;
+    }
+
+    // This branch always compiles away if the above branch wasn't removed.
+    if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
+                                !IsValueInRangeForNumericType<Promotion>(y)) &&
+                               x)) {
+      return false;
+    }
+
+    Promotion presult = Promotion(x) / Promotion(y);
+    *result = static_cast<V>(presult);
+    return IsValueInRangeForNumericType<V>(presult);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedModOp {};
+
+template <typename T, typename U>
+struct CheckedModOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    using Promotion = typename BigEnoughPromotion<T, U>::type;
+    if (BASE_NUMERICS_LIKELY(y)) {
+      Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y);
+      *result = static_cast<Promotion>(presult);
+      return IsValueInRangeForNumericType<V>(presult);
+    }
+    return false;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedLshOp {};
+
+// Left shift. Shifts less than 0 or greater than or equal to the number
+// of bits in the promoted type are undefined. Shifts of negative values
+// are undefined. Otherwise it is defined when the result fits.
+template <typename T, typename U>
+struct CheckedLshOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = T;
+  template <typename V>
+  static constexpr bool Do(T x, U shift, V* result) {
+    // Disallow negative numbers and verify the shift is in bounds.
+    if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
+                             as_unsigned(shift) <
+                                 as_unsigned(std::numeric_limits<T>::digits))) {
+      // Shift as unsigned to avoid undefined behavior.
+      *result = static_cast<V>(as_unsigned(x) << shift);
+      // If the shift can be reversed, we know it was valid.
+      return *result >> shift == x;
+    }
+
+    // Handle the legal corner-case of a full-width signed shift of zero.
+    return std::is_signed<T>::value && !x &&
+           as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedRshOp {};
+
+// Right shift. Shifts less than 0 or greater than or equal to the number
+// of bits in the promoted type are undefined. Otherwise, it is always defined,
+// but a right shift of a negative value is implementation-dependent.
+template <typename T, typename U>
+struct CheckedRshOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = T;
+  template <typename V>
+  static bool Do(T x, U shift, V* result) {
+    // Use the type conversion push negative values out of range.
+    if (BASE_NUMERICS_LIKELY(as_unsigned(shift) <
+                             IntegerBitsPlusSign<T>::value)) {
+      T tmp = x >> shift;
+      *result = static_cast<V>(tmp);
+      return IsValueInRangeForNumericType<V>(tmp);
+    }
+    return false;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedAndOp {};
+
+// For simplicity we support only unsigned integer results.
+template <typename T, typename U>
+struct CheckedAndOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
+    *result = static_cast<V>(tmp);
+    return IsValueInRangeForNumericType<V>(tmp);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedOrOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct CheckedOrOp<T,
+                   U,
+                   typename std::enable_if<std::is_integral<T>::value &&
+                                           std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
+    *result = static_cast<V>(tmp);
+    return IsValueInRangeForNumericType<V>(tmp);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedXorOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct CheckedXorOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
+    *result = static_cast<V>(tmp);
+    return IsValueInRangeForNumericType<V>(tmp);
+  }
+};
+
+// Max doesn't really need to be implemented this way because it can't fail,
+// but it makes the code much cleaner to use the MathOp wrappers.
+template <typename T, typename U, class Enable = void>
+struct CheckedMaxOp {};
+
+template <typename T, typename U>
+struct CheckedMaxOp<
+    T,
+    U,
+    typename std::enable_if<std::is_arithmetic<T>::value &&
+                            std::is_arithmetic<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
+                                                  : static_cast<result_type>(y);
+    *result = static_cast<V>(tmp);
+    return IsValueInRangeForNumericType<V>(tmp);
+  }
+};
+
+// Min doesn't really need to be implemented this way because it can't fail,
+// but it makes the code much cleaner to use the MathOp wrappers.
+template <typename T, typename U, class Enable = void>
+struct CheckedMinOp {};
+
+template <typename T, typename U>
+struct CheckedMinOp<
+    T,
+    U,
+    typename std::enable_if<std::is_arithmetic<T>::value &&
+                            std::is_arithmetic<U>::value>::type> {
+  using result_type = typename LowestValuePromotion<T, U>::type;
+  template <typename V>
+  static constexpr bool Do(T x, U y, V* result) {
+    result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
+                                               : static_cast<result_type>(y);
+    *result = static_cast<V>(tmp);
+    return IsValueInRangeForNumericType<V>(tmp);
+  }
+};
+
+// This is just boilerplate that wraps the standard floating point arithmetic.
+// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                              \
+  template <typename T, typename U>                                      \
+  struct Checked##NAME##Op<                                              \
+      T, U,                                                              \
+      typename std::enable_if<std::is_floating_point<T>::value ||        \
+                              std::is_floating_point<U>::value>::type> { \
+    using result_type = typename MaxExponentPromotion<T, U>::type;       \
+    template <typename V>                                                \
+    static constexpr bool Do(T x, U y, V* result) {                      \
+      using Promotion = typename MaxExponentPromotion<T, U>::type;       \
+      Promotion presult = x OP y;                                        \
+      *result = static_cast<V>(presult);                                 \
+      return IsValueInRangeForNumericType<V>(presult);                   \
+    }                                                                    \
+  };
+
+BASE_FLOAT_ARITHMETIC_OPS(Add, +)
+BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
+BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
+BASE_FLOAT_ARITHMETIC_OPS(Div, /)
+
+#undef BASE_FLOAT_ARITHMETIC_OPS
+
+// Floats carry around their validity state with them, but integers do not. So,
+// we wrap the underlying value in a specialization in order to hide that detail
+// and expose an interface via accessors.
+enum NumericRepresentation {
+  NUMERIC_INTEGER,
+  NUMERIC_FLOATING,
+  NUMERIC_UNKNOWN
+};
+
+template <typename NumericType>
+struct GetNumericRepresentation {
+  static const NumericRepresentation value =
+      std::is_integral<NumericType>::value
+          ? NUMERIC_INTEGER
+          : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
+                                                        : NUMERIC_UNKNOWN);
+};
+
+template <typename T,
+          NumericRepresentation type = GetNumericRepresentation<T>::value>
+class CheckedNumericState {};
+
+// Integrals require quite a bit of additional housekeeping to manage state.
+template <typename T>
+class CheckedNumericState<T, NUMERIC_INTEGER> {
+ private:
+  // is_valid_ precedes value_ because member initializers in the constructors
+  // are evaluated in field order, and is_valid_ must be read when initializing
+  // value_.
+  bool is_valid_;
+  T value_;
+
+  // Ensures that a type conversion does not trigger undefined behavior.
+  template <typename Src>
+  static constexpr T WellDefinedConversionOrZero(const Src value,
+                                                 const bool is_valid) {
+    using SrcType = typename internal::UnderlyingType<Src>::type;
+    return (std::is_integral<SrcType>::value || is_valid)
+               ? static_cast<T>(value)
+               : static_cast<T>(0);
+  }
+
+ public:
+  template <typename Src, NumericRepresentation type>
+  friend class CheckedNumericState;
+
+  constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
+
+  template <typename Src>
+  constexpr CheckedNumericState(Src value, bool is_valid)
+      : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
+        value_(WellDefinedConversionOrZero(value, is_valid_)) {
+    static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+  }
+
+  // Copy constructor.
+  template <typename Src>
+  constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
+      : is_valid_(rhs.IsValid()),
+        value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
+
+  template <typename Src>
+  constexpr explicit CheckedNumericState(Src value)
+      : is_valid_(IsValueInRangeForNumericType<T>(value)),
+        value_(WellDefinedConversionOrZero(value, is_valid_)) {}
+
+  constexpr bool is_valid() const { return is_valid_; }
+  constexpr T value() const { return value_; }
+};
+
+// Floating points maintain their own validity, but need translation wrappers.
+template <typename T>
+class CheckedNumericState<T, NUMERIC_FLOATING> {
+ private:
+  T value_;
+
+  // Ensures that a type conversion does not trigger undefined behavior.
+  template <typename Src>
+  static constexpr T WellDefinedConversionOrNaN(const Src value,
+                                                const bool is_valid) {
+    using SrcType = typename internal::UnderlyingType<Src>::type;
+    return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
+                NUMERIC_RANGE_CONTAINED ||
+            is_valid)
+               ? static_cast<T>(value)
+               : std::numeric_limits<T>::quiet_NaN();
+  }
+
+ public:
+  template <typename Src, NumericRepresentation type>
+  friend class CheckedNumericState;
+
+  constexpr CheckedNumericState() : value_(0.0) {}
+
+  template <typename Src>
+  constexpr CheckedNumericState(Src value, bool is_valid)
+      : value_(WellDefinedConversionOrNaN(value, is_valid)) {}
+
+  template <typename Src>
+  constexpr explicit CheckedNumericState(Src value)
+      : value_(WellDefinedConversionOrNaN(
+            value,
+            IsValueInRangeForNumericType<T>(value))) {}
+
+  // Copy constructor.
+  template <typename Src>
+  constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
+      : value_(WellDefinedConversionOrNaN(
+            rhs.value(),
+            rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
+
+  constexpr bool is_valid() const {
+    // Written this way because std::isfinite is not reliably constexpr.
+    return MustTreatAsConstexpr(value_)
+               ? value_ <= std::numeric_limits<T>::max() &&
+                     value_ >= std::numeric_limits<T>::lowest()
+               : std::isfinite(value_);
+  }
+  constexpr T value() const { return value_; }
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_NUMERICS_CHECKED_MATH_IMPL_H_
diff --git a/src/base/numerics/clamped_math.h b/src/base/numerics/clamped_math.h
new file mode 100644 (file)
index 0000000..9e83543
--- /dev/null
@@ -0,0 +1,262 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_CLAMPED_MATH_H_
+#define BASE_NUMERICS_CLAMPED_MATH_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/clamped_math_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+class ClampedNumeric {
+  static_assert(std::is_arithmetic<T>::value,
+                "ClampedNumeric<T>: T must be a numeric type.");
+
+ public:
+  using type = T;
+
+  constexpr ClampedNumeric() : value_(0) {}
+
+  // Copy constructor.
+  template <typename Src>
+  constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
+      : value_(saturated_cast<T>(rhs.value_)) {}
+
+  template <typename Src>
+  friend class ClampedNumeric;
+
+  // This is not an explicit constructor because we implicitly upgrade regular
+  // numerics to ClampedNumerics to make them easier to use.
+  template <typename Src>
+  constexpr ClampedNumeric(Src value)  // NOLINT(runtime/explicit)
+      : value_(saturated_cast<T>(value)) {
+    static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+  }
+
+  // This is not an explicit constructor because we want a seamless conversion
+  // from StrictNumeric types.
+  template <typename Src>
+  constexpr ClampedNumeric(
+      StrictNumeric<Src> value)  // NOLINT(runtime/explicit)
+      : value_(saturated_cast<T>(static_cast<Src>(value))) {}
+
+  // Returns a ClampedNumeric of the specified type, cast from the current
+  // ClampedNumeric, and saturated to the destination type.
+  template <typename Dst>
+  constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+    return *this;
+  }
+
+  // Prototypes for the supported arithmetic operator overloads.
+  template <typename Src>
+  constexpr ClampedNumeric& operator+=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator-=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator*=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator/=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator%=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator<<=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator>>=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator&=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator|=(const Src rhs);
+  template <typename Src>
+  constexpr ClampedNumeric& operator^=(const Src rhs);
+
+  constexpr ClampedNumeric operator-() const {
+    // The negation of two's complement int min is int min, so that's the
+    // only overflow case where we will saturate.
+    return ClampedNumeric<T>(SaturatedNegWrapper(value_));
+  }
+
+  constexpr ClampedNumeric operator~() const {
+    return ClampedNumeric<decltype(InvertWrapper(T()))>(InvertWrapper(value_));
+  }
+
+  constexpr ClampedNumeric Abs() const {
+    // The negation of two's complement int min is int min, so that's the
+    // only overflow case where we will saturate.
+    return ClampedNumeric<T>(SaturatedAbsWrapper(value_));
+  }
+
+  template <typename U>
+  constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
+      const U rhs) const {
+    using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
+    return ClampedNumeric<result_type>(
+        ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
+  }
+
+  template <typename U>
+  constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
+      const U rhs) const {
+    using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
+    return ClampedNumeric<result_type>(
+        ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
+  }
+
+  // This function is available only for integral types. It returns an unsigned
+  // integer of the same width as the source type, containing the absolute value
+  // of the source, and properly handling signed min.
+  constexpr ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>
+  UnsignedAbs() const {
+    return ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>(
+        SafeUnsignedAbs(value_));
+  }
+
+  constexpr ClampedNumeric& operator++() {
+    *this += 1;
+    return *this;
+  }
+
+  constexpr ClampedNumeric operator++(int) {
+    ClampedNumeric value = *this;
+    *this += 1;
+    return value;
+  }
+
+  constexpr ClampedNumeric& operator--() {
+    *this -= 1;
+    return *this;
+  }
+
+  constexpr ClampedNumeric operator--(int) {
+    ClampedNumeric value = *this;
+    *this -= 1;
+    return value;
+  }
+
+  // These perform the actual math operations on the ClampedNumerics.
+  // Binary arithmetic operations.
+  template <template <typename, typename, typename> class M,
+            typename L,
+            typename R>
+  static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
+    using Math = typename MathWrapper<M, L, R>::math;
+    return ClampedNumeric<T>(
+        Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
+  }
+
+  // Assignment arithmetic operations.
+  template <template <typename, typename, typename> class M, typename R>
+  constexpr ClampedNumeric& MathOp(const R rhs) {
+    using Math = typename MathWrapper<M, T, R>::math;
+    *this =
+        ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
+    return *this;
+  }
+
+  template <typename Dst>
+  constexpr operator Dst() const {
+    return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
+        value_);
+  }
+
+  // This method extracts the raw integer value without saturating it to the
+  // destination type as the conversion operator does. This is useful when
+  // e.g. assigning to an auto type or passing as a deduced template parameter.
+  constexpr T RawValue() const { return value_; }
+
+ private:
+  T value_;
+
+  // These wrappers allow us to handle state the same way for both
+  // ClampedNumeric and POD arithmetic types.
+  template <typename Src>
+  struct Wrapper {
+    static constexpr Src value(Src value) {
+      return static_cast<typename UnderlyingType<Src>::type>(value);
+    }
+  };
+};
+
+// Convience wrapper to return a new ClampedNumeric from the provided arithmetic
+// or ClampedNumericType.
+template <typename T>
+constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
+    const T value) {
+  return value;
+}
+
+// Overload the ostream output operator to make logging work nicely.
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const ClampedNumeric<T>& value) {
+  os << static_cast<T>(value);
+  return os;
+}
+
+// These implement the variadic wrapper for the math operations.
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R>
+constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
+    const L lhs,
+    const R rhs) {
+  using Math = typename MathWrapper<M, L, R>::math;
+  return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
+                                                                        rhs);
+}
+
+// General purpose wrapper template for arithmetic operations.
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R,
+          typename... Args>
+constexpr ClampedNumeric<typename ResultType<M, L, R, Args...>::type>
+ClampMathOp(const L lhs, const R rhs, const Args... args) {
+  return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
+}
+
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Add, +, +=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Sub, -, -=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mul, *, *=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Div, /, /=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mod, %, %=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Lsh, <<, <<=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Rsh, >>, >>=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, And, &, &=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Or, |, |=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Xor, ^, ^=)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Max)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Min)
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLess, <);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLessOrEqual, <=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreater, >);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreaterOrEqual, >=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsEqual, ==);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsNotEqual, !=);
+
+}  // namespace internal
+
+using internal::ClampAdd;
+using internal::ClampAnd;
+using internal::ClampDiv;
+using internal::ClampedNumeric;
+using internal::ClampLsh;
+using internal::ClampMax;
+using internal::ClampMin;
+using internal::ClampMod;
+using internal::ClampMul;
+using internal::ClampOr;
+using internal::ClampRsh;
+using internal::ClampSub;
+using internal::ClampXor;
+using internal::MakeClampedNum;
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_CLAMPED_MATH_H_
diff --git a/src/base/numerics/clamped_math_impl.h b/src/base/numerics/clamped_math_impl.h
new file mode 100644 (file)
index 0000000..303a7e9
--- /dev/null
@@ -0,0 +1,341 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
+#define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/checked_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value &&
+                                  std::is_signed<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+  return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
+             ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
+                    ? NegateWrapper(value)
+                    : std::numeric_limits<T>::max())
+             : ClampedNegFastOp<T>::Do(value);
+}
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value &&
+                                  !std::is_signed<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+  return T(0);
+}
+
+template <
+    typename T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+  return -value;
+}
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T SaturatedAbsWrapper(T value) {
+  // The calculation below is a static identity for unsigned types, but for
+  // signed integer types it provides a non-branching, saturated absolute value.
+  // This works because SafeUnsignedAbs() returns an unsigned type, which can
+  // represent the absolute value of all negative numbers of an equal-width
+  // integer type. The call to IsValueNegative() then detects overflow in the
+  // special case of numeric_limits<T>::min(), by evaluating the bit pattern as
+  // a signed integer value. If it is the overflow case, we end up subtracting
+  // one from the unsigned result, thus saturating to numeric_limits<T>::max().
+  return static_cast<T>(SafeUnsignedAbs(value) -
+                        IsValueNegative<T>(SafeUnsignedAbs(value)));
+}
+
+template <
+    typename T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T SaturatedAbsWrapper(T value) {
+  return value < 0 ? -value : value;
+}
+
+template <typename T, typename U, class Enable = void>
+struct ClampedAddOp {};
+
+template <typename T, typename U>
+struct ClampedAddOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    if (ClampedAddFastOp<T, U>::is_supported)
+      return ClampedAddFastOp<T, U>::template Do<V>(x, y);
+
+    static_assert(std::is_same<V, result_type>::value ||
+                      IsTypeInRangeForNumericType<U, V>::value,
+                  "The saturation result cannot be determined from the "
+                  "provided types.");
+    const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
+    V result = {};
+    return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
+               ? result
+               : saturated;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedSubOp {};
+
+template <typename T, typename U>
+struct ClampedSubOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    // TODO(jschuh) Make this "constexpr if" once we're C++17.
+    if (ClampedSubFastOp<T, U>::is_supported)
+      return ClampedSubFastOp<T, U>::template Do<V>(x, y);
+
+    static_assert(std::is_same<V, result_type>::value ||
+                      IsTypeInRangeForNumericType<U, V>::value,
+                  "The saturation result cannot be determined from the "
+                  "provided types.");
+    const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
+    V result = {};
+    return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
+               ? result
+               : saturated;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMulOp {};
+
+template <typename T, typename U>
+struct ClampedMulOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    // TODO(jschuh) Make this "constexpr if" once we're C++17.
+    if (ClampedMulFastOp<T, U>::is_supported)
+      return ClampedMulFastOp<T, U>::template Do<V>(x, y);
+
+    V result = {};
+    const V saturated =
+        CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
+    return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
+               ? result
+               : saturated;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedDivOp {};
+
+template <typename T, typename U>
+struct ClampedDivOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    V result = {};
+    if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
+      return result;
+    // Saturation goes to max, min, or NaN (if x is zero).
+    return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
+             : SaturationDefaultLimits<V>::NaN();
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedModOp {};
+
+template <typename T, typename U>
+struct ClampedModOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    V result = {};
+    return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
+               ? result
+               : x;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedLshOp {};
+
+// Left shift. Non-zero values saturate in the direction of the sign. A zero
+// shifted by any value always results in zero.
+template <typename T, typename U>
+struct ClampedLshOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = T;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U shift) {
+    static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+    if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
+      // Shift as unsigned to avoid undefined behavior.
+      V result = static_cast<V>(as_unsigned(x) << shift);
+      // If the shift can be reversed, we know it was valid.
+      if (BASE_NUMERICS_LIKELY(result >> shift == x))
+        return result;
+    }
+    return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedRshOp {};
+
+// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
+template <typename T, typename U>
+struct ClampedRshOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = T;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U shift) {
+    static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+    // Signed right shift is odd, because it saturates to -1 or 0.
+    const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
+    return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
+               ? saturated_cast<V>(x >> shift)
+               : saturated;
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedAndOp {};
+
+template <typename T, typename U>
+struct ClampedAndOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr V Do(T x, U y) {
+    return static_cast<result_type>(x) & static_cast<result_type>(y);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedOrOp {};
+
+// For simplicity we promote to unsigned integers.
+template <typename T, typename U>
+struct ClampedOrOp<T,
+                   U,
+                   typename std::enable_if<std::is_integral<T>::value &&
+                                           std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr V Do(T x, U y) {
+    return static_cast<result_type>(x) | static_cast<result_type>(y);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedXorOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct ClampedXorOp<T,
+                    U,
+                    typename std::enable_if<std::is_integral<T>::value &&
+                                            std::is_integral<U>::value>::type> {
+  using result_type = typename std::make_unsigned<
+      typename MaxExponentPromotion<T, U>::type>::type;
+  template <typename V>
+  static constexpr V Do(T x, U y) {
+    return static_cast<result_type>(x) ^ static_cast<result_type>(y);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMaxOp {};
+
+template <typename T, typename U>
+struct ClampedMaxOp<
+    T,
+    U,
+    typename std::enable_if<std::is_arithmetic<T>::value &&
+                            std::is_arithmetic<U>::value>::type> {
+  using result_type = typename MaxExponentPromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
+                                       : saturated_cast<V>(y);
+  }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMinOp {};
+
+template <typename T, typename U>
+struct ClampedMinOp<
+    T,
+    U,
+    typename std::enable_if<std::is_arithmetic<T>::value &&
+                            std::is_arithmetic<U>::value>::type> {
+  using result_type = typename LowestValuePromotion<T, U>::type;
+  template <typename V = result_type>
+  static constexpr V Do(T x, U y) {
+    return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
+                                    : saturated_cast<V>(y);
+  }
+};
+
+// This is just boilerplate that wraps the standard floating point arithmetic.
+// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                              \
+  template <typename T, typename U>                                      \
+  struct Clamped##NAME##Op<                                              \
+      T, U,                                                              \
+      typename std::enable_if<std::is_floating_point<T>::value ||        \
+                              std::is_floating_point<U>::value>::type> { \
+    using result_type = typename MaxExponentPromotion<T, U>::type;       \
+    template <typename V = result_type>                                  \
+    static constexpr V Do(T x, U y) {                                    \
+      return saturated_cast<V>(x OP y);                                  \
+    }                                                                    \
+  };
+
+BASE_FLOAT_ARITHMETIC_OPS(Add, +)
+BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
+BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
+BASE_FLOAT_ARITHMETIC_OPS(Div, /)
+
+#undef BASE_FLOAT_ARITHMETIC_OPS
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
diff --git a/src/base/numerics/math_constants.h b/src/base/numerics/math_constants.h
new file mode 100644 (file)
index 0000000..9a5b8ef
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_MATH_CONSTANTS_H_
+#define BASE_NUMERICS_MATH_CONSTANTS_H_
+
+namespace base {
+
+constexpr double kPiDouble = 3.14159265358979323846;
+constexpr float kPiFloat = 3.14159265358979323846f;
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_MATH_CONSTANTS_H_
diff --git a/src/base/numerics/ranges.h b/src/base/numerics/ranges.h
new file mode 100644 (file)
index 0000000..f19320c
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_RANGES_H_
+#define BASE_NUMERICS_RANGES_H_
+
+#include <algorithm>
+#include <cmath>
+
+namespace base {
+
+// To be replaced with std::clamp() from C++17, someday.
+template <class T>
+constexpr const T& ClampToRange(const T& value, const T& min, const T& max) {
+  return std::min(std::max(value, min), max);
+}
+
+template <typename T>
+constexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {
+  static_assert(std::is_arithmetic<T>::value, "Argument must be arithmetic");
+  return std::abs(rhs - lhs) <= tolerance;
+}
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_RANGES_H_
diff --git a/src/base/numerics/safe_conversions.h b/src/base/numerics/safe_conversions.h
new file mode 100644 (file)
index 0000000..35c4089
--- /dev/null
@@ -0,0 +1,339 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_H_
+#define BASE_NUMERICS_SAFE_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <ostream>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions_impl.h"
+
+#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
+
+namespace base {
+namespace internal {
+
+#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+template <typename Dst, typename Src>
+struct SaturateFastAsmOp {
+  static const bool is_supported = false;
+  static constexpr Dst Do(Src) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<Dst>();
+  }
+};
+#endif  // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+
+// The following special case a few specific integer conversions where we can
+// eke out better performance than range checking.
+template <typename Dst, typename Src, typename Enable = void>
+struct IsValueInRangeFastOp {
+  static const bool is_supported = false;
+  static constexpr bool Do(Src value) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<bool>();
+  }
+};
+
+// Signed to signed range comparison.
+template <typename Dst, typename Src>
+struct IsValueInRangeFastOp<
+    Dst,
+    Src,
+    typename std::enable_if<
+        std::is_integral<Dst>::value && std::is_integral<Src>::value &&
+        std::is_signed<Dst>::value && std::is_signed<Src>::value &&
+        !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+  static const bool is_supported = true;
+
+  static constexpr bool Do(Src value) {
+    // Just downcast to the smaller type, sign extend it back to the original
+    // type, and then see if it matches the original value.
+    return value == static_cast<Dst>(value);
+  }
+};
+
+// Signed to unsigned range comparison.
+template <typename Dst, typename Src>
+struct IsValueInRangeFastOp<
+    Dst,
+    Src,
+    typename std::enable_if<
+        std::is_integral<Dst>::value && std::is_integral<Src>::value &&
+        !std::is_signed<Dst>::value && std::is_signed<Src>::value &&
+        !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+  static const bool is_supported = true;
+
+  static constexpr bool Do(Src value) {
+    // We cast a signed as unsigned to overflow negative values to the top,
+    // then compare against whichever maximum is smaller, as our upper bound.
+    return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
+  }
+};
+
+// Convenience function that returns true if the supplied value is in range
+// for the destination type.
+template <typename Dst, typename Src>
+constexpr bool IsValueInRangeForNumericType(Src value) {
+  using SrcType = typename internal::UnderlyingType<Src>::type;
+  return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
+             ? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
+                   static_cast<SrcType>(value))
+             : internal::DstRangeRelationToSrcRange<Dst>(
+                   static_cast<SrcType>(value))
+                   .IsValid();
+}
+
+// checked_cast<> is analogous to static_cast<> for numeric types,
+// except that it CHECKs that the specified numeric conversion will not
+// overflow or underflow. NaN source will always trigger a CHECK.
+template <typename Dst,
+          class CheckHandler = internal::CheckOnFailure,
+          typename Src>
+constexpr Dst checked_cast(Src value) {
+  // This throws a compile-time error on evaluating the constexpr if it can be
+  // determined at compile-time as failing, otherwise it will CHECK at runtime.
+  using SrcType = typename internal::UnderlyingType<Src>::type;
+  return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
+             ? static_cast<Dst>(static_cast<SrcType>(value))
+             : CheckHandler::template HandleFailure<Dst>();
+}
+
+// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
+// You may provide your own limits (e.g. to saturated_cast) so long as you
+// implement all of the static constexpr member functions in the class below.
+template <typename T>
+struct SaturationDefaultLimits : public std::numeric_limits<T> {
+  static constexpr T NaN() {
+    return std::numeric_limits<T>::has_quiet_NaN
+               ? std::numeric_limits<T>::quiet_NaN()
+               : T();
+  }
+  using std::numeric_limits<T>::max;
+  static constexpr T Overflow() {
+    return std::numeric_limits<T>::has_infinity
+               ? std::numeric_limits<T>::infinity()
+               : std::numeric_limits<T>::max();
+  }
+  using std::numeric_limits<T>::lowest;
+  static constexpr T Underflow() {
+    return std::numeric_limits<T>::has_infinity
+               ? std::numeric_limits<T>::infinity() * -1
+               : std::numeric_limits<T>::lowest();
+  }
+};
+
+template <typename Dst, template <typename> class S, typename Src>
+constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
+  // For some reason clang generates much better code when the branch is
+  // structured exactly this way, rather than a sequence of checks.
+  return !constraint.IsOverflowFlagSet()
+             ? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
+                                                 : S<Dst>::Underflow())
+             // Skip this check for integral Src, which cannot be NaN.
+             : (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
+                    ? S<Dst>::Overflow()
+                    : S<Dst>::NaN());
+}
+
+// We can reduce the number of conditions and get slightly better performance
+// for normal signed and unsigned integer ranges. And in the specific case of
+// Arm, we can use the optimized saturation instructions.
+template <typename Dst, typename Src, typename Enable = void>
+struct SaturateFastOp {
+  static const bool is_supported = false;
+  static constexpr Dst Do(Src value) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<Dst>();
+  }
+};
+
+template <typename Dst, typename Src>
+struct SaturateFastOp<
+    Dst,
+    Src,
+    typename std::enable_if<std::is_integral<Src>::value &&
+                            std::is_integral<Dst>::value>::type> {
+  static const bool is_supported = true;
+  static Dst Do(Src value) {
+    if (SaturateFastAsmOp<Dst, Src>::is_supported)
+      return SaturateFastAsmOp<Dst, Src>::Do(value);
+
+    // The exact order of the following is structured to hit the correct
+    // optimization heuristics across compilers. Do not change without
+    // checking the emitted code.
+    Dst saturated = CommonMaxOrMin<Dst, Src>(
+        IsMaxInRangeForNumericType<Dst, Src>() ||
+        (!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
+    return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
+               ? static_cast<Dst>(value)
+               : saturated;
+  }
+};
+
+// saturated_cast<> is analogous to static_cast<> for numeric types, except
+// that the specified numeric conversion will saturate by default rather than
+// overflow or underflow, and NaN assignment to an integral will return 0.
+// All boundary condition behaviors can be overridden with a custom handler.
+template <typename Dst,
+          template <typename> class SaturationHandler = SaturationDefaultLimits,
+          typename Src>
+constexpr Dst saturated_cast(Src value) {
+  using SrcType = typename UnderlyingType<Src>::type;
+  return !IsCompileTimeConstant(value) &&
+                 SaturateFastOp<Dst, SrcType>::is_supported &&
+                 std::is_same<SaturationHandler<Dst>,
+                              SaturationDefaultLimits<Dst>>::value
+             ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
+             : saturated_cast_impl<Dst, SaturationHandler, SrcType>(
+                   static_cast<SrcType>(value),
+                   DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
+                       static_cast<SrcType>(value)));
+}
+
+// strict_cast<> is analogous to static_cast<> for numeric types, except that
+// it will cause a compile failure if the destination type is not large enough
+// to contain any value in the source type. It performs no runtime checking.
+template <typename Dst, typename Src>
+constexpr Dst strict_cast(Src value) {
+  using SrcType = typename UnderlyingType<Src>::type;
+  static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
+  static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
+
+  // If you got here from a compiler error, it's because you tried to assign
+  // from a source type to a destination type that has insufficient range.
+  // The solution may be to change the destination type you're assigning to,
+  // and use one large enough to represent the source.
+  // Alternatively, you may be better served with the checked_cast<> or
+  // saturated_cast<> template functions for your particular use case.
+  static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
+                    NUMERIC_RANGE_CONTAINED,
+                "The source type is out of range for the destination type. "
+                "Please see strict_cast<> comments for more information.");
+
+  return static_cast<Dst>(static_cast<SrcType>(value));
+}
+
+// Some wrappers to statically check that a type is in range.
+template <typename Dst, typename Src, class Enable = void>
+struct IsNumericRangeContained {
+  static const bool value = false;
+};
+
+template <typename Dst, typename Src>
+struct IsNumericRangeContained<
+    Dst,
+    Src,
+    typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
+                            ArithmeticOrUnderlyingEnum<Src>::value>::type> {
+  static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
+                            NUMERIC_RANGE_CONTAINED;
+};
+
+// StrictNumeric implements compile time range checking between numeric types by
+// wrapping assignment operations in a strict_cast. This class is intended to be
+// used for function arguments and return types, to ensure the destination type
+// can always contain the source type. This is essentially the same as enforcing
+// -Wconversion in gcc and C4302 warnings on MSVC, but it can be applied
+// incrementally at API boundaries, making it easier to convert code so that it
+// compiles cleanly with truncation warnings enabled.
+// This template should introduce no runtime overhead, but it also provides no
+// runtime checking of any of the associated mathematical operations. Use
+// CheckedNumeric for runtime range checks of the actual value being assigned.
+template <typename T>
+class StrictNumeric {
+ public:
+  using type = T;
+
+  constexpr StrictNumeric() : value_(0) {}
+
+  // Copy constructor.
+  template <typename Src>
+  constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
+      : value_(strict_cast<T>(rhs.value_)) {}
+
+  // This is not an explicit constructor because we implicitly upgrade regular
+  // numerics to StrictNumerics to make them easier to use.
+  template <typename Src>
+  constexpr StrictNumeric(Src value)  // NOLINT(runtime/explicit)
+      : value_(strict_cast<T>(value)) {}
+
+  // If you got here from a compiler error, it's because you tried to assign
+  // from a source type to a destination type that has insufficient range.
+  // The solution may be to change the destination type you're assigning to,
+  // and use one large enough to represent the source.
+  // If you're assigning from a CheckedNumeric<> class, you may be able to use
+  // the AssignIfValid() member function, specify a narrower destination type to
+  // the member value functions (e.g. val.template ValueOrDie<Dst>()), use one
+  // of the value helper functions (e.g. ValueOrDieForType<Dst>(val)).
+  // If you've encountered an _ambiguous overload_ you can use a static_cast<>
+  // to explicitly cast the result to the destination type.
+  // If none of that works, you may be better served with the checked_cast<> or
+  // saturated_cast<> template functions for your particular use case.
+  template <typename Dst,
+            typename std::enable_if<
+                IsNumericRangeContained<Dst, T>::value>::type* = nullptr>
+  constexpr operator Dst() const {
+    return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
+  }
+
+ private:
+  const T value_;
+};
+
+// Convience wrapper returns a StrictNumeric from the provided arithmetic type.
+template <typename T>
+constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
+    const T value) {
+  return value;
+}
+
+// Overload the ostream output operator to make logging work nicely.
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
+  os << static_cast<T>(value);
+  return os;
+}
+
+#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP)              \
+  template <typename L, typename R,                                     \
+            typename std::enable_if<                                    \
+                internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
+  constexpr bool operator OP(const L lhs, const R rhs) {                \
+    return SafeCompare<NAME, typename UnderlyingType<L>::type,          \
+                       typename UnderlyingType<R>::type>(lhs, rhs);     \
+  }
+
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=);
+
+};  // namespace internal
+
+using internal::as_signed;
+using internal::as_unsigned;
+using internal::checked_cast;
+using internal::IsTypeInRangeForNumericType;
+using internal::IsValueInRangeForNumericType;
+using internal::IsValueNegative;
+using internal::MakeStrictNum;
+using internal::SafeUnsignedAbs;
+using internal::saturated_cast;
+using internal::strict_cast;
+using internal::StrictNumeric;
+
+// Explicitly make a shorter size_t alias for convenience.
+using SizeT = StrictNumeric<size_t>;
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_SAFE_CONVERSIONS_H_
diff --git a/src/base/numerics/safe_conversions_impl.h b/src/base/numerics/safe_conversions_impl.h
new file mode 100644 (file)
index 0000000..8ae6a54
--- /dev/null
@@ -0,0 +1,848 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
+#define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
+
+#include <stdint.h>
+
+#include <limits>
+#include <type_traits>
+
+#if defined(__GNUC__) || defined(__clang__)
+#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
+#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define BASE_NUMERICS_LIKELY(x) (x)
+#define BASE_NUMERICS_UNLIKELY(x) (x)
+#endif
+
+namespace base {
+namespace internal {
+
+// The std library doesn't provide a binary max_exponent for integers, however
+// we can compute an analog using std::numeric_limits<>::digits.
+template <typename NumericType>
+struct MaxExponent {
+  static const int value = std::is_floating_point<NumericType>::value
+                               ? std::numeric_limits<NumericType>::max_exponent
+                               : std::numeric_limits<NumericType>::digits + 1;
+};
+
+// The number of bits (including the sign) in an integer. Eliminates sizeof
+// hacks.
+template <typename NumericType>
+struct IntegerBitsPlusSign {
+  static const int value = std::numeric_limits<NumericType>::digits +
+                           std::is_signed<NumericType>::value;
+};
+
+// Helper templates for integer manipulations.
+
+template <typename Integer>
+struct PositionOfSignBit {
+  static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
+};
+
+// Determines if a numeric value is negative without throwing compiler
+// warnings on: unsigned(value) < 0.
+template <typename T,
+          typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
+constexpr bool IsValueNegative(T value) {
+  static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
+  return value < 0;
+}
+
+template <typename T,
+          typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
+constexpr bool IsValueNegative(T) {
+  static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
+  return false;
+}
+
+// This performs a fast negation, returning a signed value. It works on unsigned
+// arguments, but probably doesn't do what you want for any unsigned value
+// larger than max / 2 + 1 (i.e. signed min cast to unsigned).
+template <typename T>
+constexpr typename std::make_signed<T>::type ConditionalNegate(
+    T x,
+    bool is_negative) {
+  static_assert(std::is_integral<T>::value, "Type must be integral");
+  using SignedT = typename std::make_signed<T>::type;
+  using UnsignedT = typename std::make_unsigned<T>::type;
+  return static_cast<SignedT>(
+      (static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
+}
+
+// This performs a safe, absolute value via unsigned overflow.
+template <typename T>
+constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
+  static_assert(std::is_integral<T>::value, "Type must be integral");
+  using UnsignedT = typename std::make_unsigned<T>::type;
+  return IsValueNegative(value) ? 0 - static_cast<UnsignedT>(value)
+                                : static_cast<UnsignedT>(value);
+}
+
+// This allows us to switch paths on known compile-time constants.
+#if defined(__clang__) || defined(__GNUC__)
+constexpr bool CanDetectCompileTimeConstant() {
+  return true;
+}
+template <typename T>
+constexpr bool IsCompileTimeConstant(const T v) {
+  return __builtin_constant_p(v);
+}
+#else
+constexpr bool CanDetectCompileTimeConstant() {
+  return false;
+}
+template <typename T>
+constexpr bool IsCompileTimeConstant(const T) {
+  return false;
+}
+#endif
+template <typename T>
+constexpr bool MustTreatAsConstexpr(const T v) {
+  // Either we can't detect a compile-time constant, and must always use the
+  // constexpr path, or we know we have a compile-time constant.
+  return !CanDetectCompileTimeConstant() || IsCompileTimeConstant(v);
+}
+
+// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
+// Also used in a constexpr template to trigger a compilation failure on
+// an error condition.
+struct CheckOnFailure {
+  template <typename T>
+  static T HandleFailure() {
+#if defined(_MSC_VER)
+    __debugbreak();
+#elif defined(__GNUC__) || defined(__clang__)
+    __builtin_trap();
+#else
+    ((void)(*(volatile char*)0 = 0));
+#endif
+    return T();
+  }
+};
+
+enum IntegerRepresentation {
+  INTEGER_REPRESENTATION_UNSIGNED,
+  INTEGER_REPRESENTATION_SIGNED
+};
+
+// A range for a given nunmeric Src type is contained for a given numeric Dst
+// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
+// numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
+// We implement this as template specializations rather than simple static
+// comparisons to ensure type correctness in our comparisons.
+enum NumericRangeRepresentation {
+  NUMERIC_RANGE_NOT_CONTAINED,
+  NUMERIC_RANGE_CONTAINED
+};
+
+// Helper templates to statically determine if our destination type can contain
+// maximum and minimum values represented by the source type.
+
+template <typename Dst,
+          typename Src,
+          IntegerRepresentation DstSign = std::is_signed<Dst>::value
+                                              ? INTEGER_REPRESENTATION_SIGNED
+                                              : INTEGER_REPRESENTATION_UNSIGNED,
+          IntegerRepresentation SrcSign = std::is_signed<Src>::value
+                                              ? INTEGER_REPRESENTATION_SIGNED
+                                              : INTEGER_REPRESENTATION_UNSIGNED>
+struct StaticDstRangeRelationToSrcRange;
+
+// Same sign: Dst is guaranteed to contain Src only if its range is equal or
+// larger.
+template <typename Dst, typename Src, IntegerRepresentation Sign>
+struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> {
+  static const NumericRangeRepresentation value =
+      MaxExponent<Dst>::value >= MaxExponent<Src>::value
+          ? NUMERIC_RANGE_CONTAINED
+          : NUMERIC_RANGE_NOT_CONTAINED;
+};
+
+// Unsigned to signed: Dst is guaranteed to contain source only if its range is
+// larger.
+template <typename Dst, typename Src>
+struct StaticDstRangeRelationToSrcRange<Dst,
+                                        Src,
+                                        INTEGER_REPRESENTATION_SIGNED,
+                                        INTEGER_REPRESENTATION_UNSIGNED> {
+  static const NumericRangeRepresentation value =
+      MaxExponent<Dst>::value > MaxExponent<Src>::value
+          ? NUMERIC_RANGE_CONTAINED
+          : NUMERIC_RANGE_NOT_CONTAINED;
+};
+
+// Signed to unsigned: Dst cannot be statically determined to contain Src.
+template <typename Dst, typename Src>
+struct StaticDstRangeRelationToSrcRange<Dst,
+                                        Src,
+                                        INTEGER_REPRESENTATION_UNSIGNED,
+                                        INTEGER_REPRESENTATION_SIGNED> {
+  static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
+};
+
+// This class wraps the range constraints as separate booleans so the compiler
+// can identify constants and eliminate unused code paths.
+class RangeCheck {
+ public:
+  constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
+      : is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
+  constexpr RangeCheck() : is_underflow_(0), is_overflow_(0) {}
+  constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
+  constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
+  constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
+  constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
+  constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
+  constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
+  constexpr bool operator==(const RangeCheck rhs) const {
+    return is_underflow_ == rhs.is_underflow_ &&
+           is_overflow_ == rhs.is_overflow_;
+  }
+  constexpr bool operator!=(const RangeCheck rhs) const {
+    return !(*this == rhs);
+  }
+
+ private:
+  // Do not change the order of these member variables. The integral conversion
+  // optimization depends on this exact order.
+  const bool is_underflow_;
+  const bool is_overflow_;
+};
+
+// The following helper template addresses a corner case in range checks for
+// conversion from a floating-point type to an integral type of smaller range
+// but larger precision (e.g. float -> unsigned). The problem is as follows:
+//   1. Integral maximum is always one less than a power of two, so it must be
+//      truncated to fit the mantissa of the floating point. The direction of
+//      rounding is implementation defined, but by default it's always IEEE
+//      floats, which round to nearest and thus result in a value of larger
+//      magnitude than the integral value.
+//      Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX
+//                                   // is 4294967295u.
+//   2. If the floating point value is equal to the promoted integral maximum
+//      value, a range check will erroneously pass.
+//      Example: (4294967296f <= 4294967295u) // This is true due to a precision
+//                                            // loss in rounding up to float.
+//   3. When the floating point value is then converted to an integral, the
+//      resulting value is out of range for the target integral type and
+//      thus is implementation defined.
+//      Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0.
+// To fix this bug we manually truncate the maximum value when the destination
+// type is an integral of larger precision than the source floating-point type,
+// such that the resulting maximum is represented exactly as a floating point.
+template <typename Dst, typename Src, template <typename> class Bounds>
+struct NarrowingRange {
+  using SrcLimits = std::numeric_limits<Src>;
+  using DstLimits = typename std::numeric_limits<Dst>;
+
+  // Computes the mask required to make an accurate comparison between types.
+  static const int kShift =
+      (MaxExponent<Src>::value > MaxExponent<Dst>::value &&
+       SrcLimits::digits < DstLimits::digits)
+          ? (DstLimits::digits - SrcLimits::digits)
+          : 0;
+  template <
+      typename T,
+      typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+
+  // Masks out the integer bits that are beyond the precision of the
+  // intermediate type used for comparison.
+  static constexpr T Adjust(T value) {
+    static_assert(std::is_same<T, Dst>::value, "");
+    static_assert(kShift < DstLimits::digits, "");
+    return static_cast<T>(
+        ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
+                          IsValueNegative(value)));
+  }
+
+  template <typename T,
+            typename std::enable_if<std::is_floating_point<T>::value>::type* =
+                nullptr>
+  static constexpr T Adjust(T value) {
+    static_assert(std::is_same<T, Dst>::value, "");
+    static_assert(kShift == 0, "");
+    return value;
+  }
+
+  static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
+  static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
+};
+
+template <typename Dst,
+          typename Src,
+          template <typename>
+          class Bounds,
+          IntegerRepresentation DstSign = std::is_signed<Dst>::value
+                                              ? INTEGER_REPRESENTATION_SIGNED
+                                              : INTEGER_REPRESENTATION_UNSIGNED,
+          IntegerRepresentation SrcSign = std::is_signed<Src>::value
+                                              ? INTEGER_REPRESENTATION_SIGNED
+                                              : INTEGER_REPRESENTATION_UNSIGNED,
+          NumericRangeRepresentation DstRange =
+              StaticDstRangeRelationToSrcRange<Dst, Src>::value>
+struct DstRangeRelationToSrcRangeImpl;
+
+// The following templates are for ranges that must be verified at runtime. We
+// split it into checks based on signedness to avoid confusing casts and
+// compiler warnings on signed an unsigned comparisons.
+
+// Same sign narrowing: The range is contained for normal limits.
+template <typename Dst,
+          typename Src,
+          template <typename>
+          class Bounds,
+          IntegerRepresentation DstSign,
+          IntegerRepresentation SrcSign>
+struct DstRangeRelationToSrcRangeImpl<Dst,
+                                      Src,
+                                      Bounds,
+                                      DstSign,
+                                      SrcSign,
+                                      NUMERIC_RANGE_CONTAINED> {
+  static constexpr RangeCheck Check(Src value) {
+    using SrcLimits = std::numeric_limits<Src>;
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeCheck(
+        static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
+            static_cast<Dst>(value) >= DstLimits::lowest(),
+        static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
+            static_cast<Dst>(value) <= DstLimits::max());
+  }
+};
+
+// Signed to signed narrowing: Both the upper and lower boundaries may be
+// exceeded for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
+struct DstRangeRelationToSrcRangeImpl<Dst,
+                                      Src,
+                                      Bounds,
+                                      INTEGER_REPRESENTATION_SIGNED,
+                                      INTEGER_REPRESENTATION_SIGNED,
+                                      NUMERIC_RANGE_NOT_CONTAINED> {
+  static constexpr RangeCheck Check(Src value) {
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
+  }
+};
+
+// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
+// standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
+struct DstRangeRelationToSrcRangeImpl<Dst,
+                                      Src,
+                                      Bounds,
+                                      INTEGER_REPRESENTATION_UNSIGNED,
+                                      INTEGER_REPRESENTATION_UNSIGNED,
+                                      NUMERIC_RANGE_NOT_CONTAINED> {
+  static constexpr RangeCheck Check(Src value) {
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeCheck(
+        DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
+        value <= DstLimits::max());
+  }
+};
+
+// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
+struct DstRangeRelationToSrcRangeImpl<Dst,
+                                      Src,
+                                      Bounds,
+                                      INTEGER_REPRESENTATION_SIGNED,
+                                      INTEGER_REPRESENTATION_UNSIGNED,
+                                      NUMERIC_RANGE_NOT_CONTAINED> {
+  static constexpr RangeCheck Check(Src value) {
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    using Promotion = decltype(Src() + Dst());
+    return RangeCheck(DstLimits::lowest() <= Dst(0) ||
+                          static_cast<Promotion>(value) >=
+                              static_cast<Promotion>(DstLimits::lowest()),
+                      static_cast<Promotion>(value) <=
+                          static_cast<Promotion>(DstLimits::max()));
+  }
+};
+
+// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
+// and any negative value exceeds the lower boundary for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
+struct DstRangeRelationToSrcRangeImpl<Dst,
+                                      Src,
+                                      Bounds,
+                                      INTEGER_REPRESENTATION_UNSIGNED,
+                                      INTEGER_REPRESENTATION_SIGNED,
+                                      NUMERIC_RANGE_NOT_CONTAINED> {
+  static constexpr RangeCheck Check(Src value) {
+    using SrcLimits = std::numeric_limits<Src>;
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    using Promotion = decltype(Src() + Dst());
+    return RangeCheck(
+        value >= Src(0) && (DstLimits::lowest() == 0 ||
+                            static_cast<Dst>(value) >= DstLimits::lowest()),
+        static_cast<Promotion>(SrcLimits::max()) <=
+                static_cast<Promotion>(DstLimits::max()) ||
+            static_cast<Promotion>(value) <=
+                static_cast<Promotion>(DstLimits::max()));
+  }
+};
+
+// Simple wrapper for statically checking if a type's range is contained.
+template <typename Dst, typename Src>
+struct IsTypeInRangeForNumericType {
+  static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
+                            NUMERIC_RANGE_CONTAINED;
+};
+
+template <typename Dst,
+          template <typename> class Bounds = std::numeric_limits,
+          typename Src>
+constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
+  static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+  static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
+  static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
+  return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
+}
+
+// Integer promotion templates used by the portable checked integer arithmetic.
+template <size_t Size, bool IsSigned>
+struct IntegerForDigitsAndSign;
+
+#define INTEGER_FOR_DIGITS_AND_SIGN(I)                          \
+  template <>                                                   \
+  struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
+                                 std::is_signed<I>::value> {    \
+    using type = I;                                             \
+  }
+
+INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
+INTEGER_FOR_DIGITS_AND_SIGN(uint8_t);
+INTEGER_FOR_DIGITS_AND_SIGN(int16_t);
+INTEGER_FOR_DIGITS_AND_SIGN(uint16_t);
+INTEGER_FOR_DIGITS_AND_SIGN(int32_t);
+INTEGER_FOR_DIGITS_AND_SIGN(uint32_t);
+INTEGER_FOR_DIGITS_AND_SIGN(int64_t);
+INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
+#undef INTEGER_FOR_DIGITS_AND_SIGN
+
+// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
+// support 128-bit math, then the ArithmeticPromotion template below will need
+// to be updated (or more likely replaced with a decltype expression).
+static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
+              "Max integer size not supported for this toolchain.");
+
+template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
+struct TwiceWiderInteger {
+  using type =
+      typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
+                                       IsSigned>::type;
+};
+
+enum ArithmeticPromotionCategory {
+  LEFT_PROMOTION,  // Use the type of the left-hand argument.
+  RIGHT_PROMOTION  // Use the type of the right-hand argument.
+};
+
+// Determines the type that can represent the largest positive value.
+template <typename Lhs,
+          typename Rhs,
+          ArithmeticPromotionCategory Promotion =
+              (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
+                  ? LEFT_PROMOTION
+                  : RIGHT_PROMOTION>
+struct MaxExponentPromotion;
+
+template <typename Lhs, typename Rhs>
+struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
+  using type = Lhs;
+};
+
+template <typename Lhs, typename Rhs>
+struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
+  using type = Rhs;
+};
+
+// Determines the type that can represent the lowest arithmetic value.
+template <typename Lhs,
+          typename Rhs,
+          ArithmeticPromotionCategory Promotion =
+              std::is_signed<Lhs>::value
+                  ? (std::is_signed<Rhs>::value
+                         ? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
+                                ? LEFT_PROMOTION
+                                : RIGHT_PROMOTION)
+                         : LEFT_PROMOTION)
+                  : (std::is_signed<Rhs>::value
+                         ? RIGHT_PROMOTION
+                         : (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
+                                ? LEFT_PROMOTION
+                                : RIGHT_PROMOTION))>
+struct LowestValuePromotion;
+
+template <typename Lhs, typename Rhs>
+struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
+  using type = Lhs;
+};
+
+template <typename Lhs, typename Rhs>
+struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
+  using type = Rhs;
+};
+
+// Determines the type that is best able to represent an arithmetic result.
+template <
+    typename Lhs,
+    typename Rhs = Lhs,
+    bool is_intmax_type =
+        std::is_integral<typename MaxExponentPromotion<Lhs, Rhs>::type>::value&&
+            IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
+                value == IntegerBitsPlusSign<intmax_t>::value,
+    bool is_max_exponent =
+        StaticDstRangeRelationToSrcRange<
+            typename MaxExponentPromotion<Lhs, Rhs>::type,
+            Lhs>::value ==
+        NUMERIC_RANGE_CONTAINED&& StaticDstRangeRelationToSrcRange<
+            typename MaxExponentPromotion<Lhs, Rhs>::type,
+            Rhs>::value == NUMERIC_RANGE_CONTAINED>
+struct BigEnoughPromotion;
+
+// The side with the max exponent is big enough.
+template <typename Lhs, typename Rhs, bool is_intmax_type>
+struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
+  using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
+  static const bool is_contained = true;
+};
+
+// We can use a twice wider type to fit.
+template <typename Lhs, typename Rhs>
+struct BigEnoughPromotion<Lhs, Rhs, false, false> {
+  using type =
+      typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
+                                 std::is_signed<Lhs>::value ||
+                                     std::is_signed<Rhs>::value>::type;
+  static const bool is_contained = true;
+};
+
+// No type is large enough.
+template <typename Lhs, typename Rhs>
+struct BigEnoughPromotion<Lhs, Rhs, true, false> {
+  using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
+  static const bool is_contained = false;
+};
+
+// We can statically check if operations on the provided types can wrap, so we
+// can skip the checked operations if they're not needed. So, for an integer we
+// care if the destination type preserves the sign and is twice the width of
+// the source.
+template <typename T, typename Lhs, typename Rhs = Lhs>
+struct IsIntegerArithmeticSafe {
+  static const bool value =
+      !std::is_floating_point<T>::value &&
+      !std::is_floating_point<Lhs>::value &&
+      !std::is_floating_point<Rhs>::value &&
+      std::is_signed<T>::value >= std::is_signed<Lhs>::value &&
+      IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
+      std::is_signed<T>::value >= std::is_signed<Rhs>::value &&
+      IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
+};
+
+// Promotes to a type that can represent any possible result of a binary
+// arithmetic operation with the source types.
+template <typename Lhs,
+          typename Rhs,
+          bool is_promotion_possible = IsIntegerArithmeticSafe<
+              typename std::conditional<std::is_signed<Lhs>::value ||
+                                            std::is_signed<Rhs>::value,
+                                        intmax_t,
+                                        uintmax_t>::type,
+              typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
+struct FastIntegerArithmeticPromotion;
+
+template <typename Lhs, typename Rhs>
+struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
+  using type =
+      typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
+                                 std::is_signed<Lhs>::value ||
+                                     std::is_signed<Rhs>::value>::type;
+  static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
+  static const bool is_contained = true;
+};
+
+template <typename Lhs, typename Rhs>
+struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
+  using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
+  static const bool is_contained = false;
+};
+
+// Extracts the underlying type from an enum.
+template <typename T, bool is_enum = std::is_enum<T>::value>
+struct ArithmeticOrUnderlyingEnum;
+
+template <typename T>
+struct ArithmeticOrUnderlyingEnum<T, true> {
+  using type = typename std::underlying_type<T>::type;
+  static const bool value = std::is_arithmetic<type>::value;
+};
+
+template <typename T>
+struct ArithmeticOrUnderlyingEnum<T, false> {
+  using type = T;
+  static const bool value = std::is_arithmetic<type>::value;
+};
+
+// The following are helper templates used in the CheckedNumeric class.
+template <typename T>
+class CheckedNumeric;
+
+template <typename T>
+class ClampedNumeric;
+
+template <typename T>
+class StrictNumeric;
+
+// Used to treat CheckedNumeric and arithmetic underlying types the same.
+template <typename T>
+struct UnderlyingType {
+  using type = typename ArithmeticOrUnderlyingEnum<T>::type;
+  static const bool is_numeric = std::is_arithmetic<type>::value;
+  static const bool is_checked = false;
+  static const bool is_clamped = false;
+  static const bool is_strict = false;
+};
+
+template <typename T>
+struct UnderlyingType<CheckedNumeric<T>> {
+  using type = T;
+  static const bool is_numeric = true;
+  static const bool is_checked = true;
+  static const bool is_clamped = false;
+  static const bool is_strict = false;
+};
+
+template <typename T>
+struct UnderlyingType<ClampedNumeric<T>> {
+  using type = T;
+  static const bool is_numeric = true;
+  static const bool is_checked = false;
+  static const bool is_clamped = true;
+  static const bool is_strict = false;
+};
+
+template <typename T>
+struct UnderlyingType<StrictNumeric<T>> {
+  using type = T;
+  static const bool is_numeric = true;
+  static const bool is_checked = false;
+  static const bool is_clamped = false;
+  static const bool is_strict = true;
+};
+
+template <typename L, typename R>
+struct IsCheckedOp {
+  static const bool value =
+      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
+      (UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
+};
+
+template <typename L, typename R>
+struct IsClampedOp {
+  static const bool value =
+      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
+      (UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
+      !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
+};
+
+template <typename L, typename R>
+struct IsStrictOp {
+  static const bool value =
+      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
+      (UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
+      !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
+      !(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
+};
+
+// as_signed<> returns the supplied integral value (or integral castable
+// Numeric template) cast as a signed integral of equivalent precision.
+// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
+template <typename Src>
+constexpr typename std::make_signed<
+    typename base::internal::UnderlyingType<Src>::type>::type
+as_signed(const Src value) {
+  static_assert(std::is_integral<decltype(as_signed(value))>::value,
+                "Argument must be a signed or unsigned integer type.");
+  return static_cast<decltype(as_signed(value))>(value);
+}
+
+// as_unsigned<> returns the supplied integral value (or integral castable
+// Numeric template) cast as an unsigned integral of equivalent precision.
+// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
+template <typename Src>
+constexpr typename std::make_unsigned<
+    typename base::internal::UnderlyingType<Src>::type>::type
+as_unsigned(const Src value) {
+  static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
+                "Argument must be a signed or unsigned integer type.");
+  return static_cast<decltype(as_unsigned(value))>(value);
+}
+
+template <typename L, typename R>
+constexpr bool IsLessImpl(const L lhs,
+                          const R rhs,
+                          const RangeCheck l_range,
+                          const RangeCheck r_range) {
+  return l_range.IsUnderflow() || r_range.IsOverflow() ||
+         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <
+                                    static_cast<decltype(lhs + rhs)>(rhs));
+}
+
+template <typename L, typename R>
+struct IsLess {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
+                      DstRangeRelationToSrcRange<L>(rhs));
+  }
+};
+
+template <typename L, typename R>
+constexpr bool IsLessOrEqualImpl(const L lhs,
+                                 const R rhs,
+                                 const RangeCheck l_range,
+                                 const RangeCheck r_range) {
+  return l_range.IsUnderflow() || r_range.IsOverflow() ||
+         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <=
+                                    static_cast<decltype(lhs + rhs)>(rhs));
+}
+
+template <typename L, typename R>
+struct IsLessOrEqual {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
+                             DstRangeRelationToSrcRange<L>(rhs));
+  }
+};
+
+template <typename L, typename R>
+constexpr bool IsGreaterImpl(const L lhs,
+                             const R rhs,
+                             const RangeCheck l_range,
+                             const RangeCheck r_range) {
+  return l_range.IsOverflow() || r_range.IsUnderflow() ||
+         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >
+                                    static_cast<decltype(lhs + rhs)>(rhs));
+}
+
+template <typename L, typename R>
+struct IsGreater {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
+                         DstRangeRelationToSrcRange<L>(rhs));
+  }
+};
+
+template <typename L, typename R>
+constexpr bool IsGreaterOrEqualImpl(const L lhs,
+                                    const R rhs,
+                                    const RangeCheck l_range,
+                                    const RangeCheck r_range) {
+  return l_range.IsOverflow() || r_range.IsUnderflow() ||
+         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >=
+                                    static_cast<decltype(lhs + rhs)>(rhs));
+}
+
+template <typename L, typename R>
+struct IsGreaterOrEqual {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
+                                DstRangeRelationToSrcRange<L>(rhs));
+  }
+};
+
+template <typename L, typename R>
+struct IsEqual {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return DstRangeRelationToSrcRange<R>(lhs) ==
+               DstRangeRelationToSrcRange<L>(rhs) &&
+           static_cast<decltype(lhs + rhs)>(lhs) ==
+               static_cast<decltype(lhs + rhs)>(rhs);
+  }
+};
+
+template <typename L, typename R>
+struct IsNotEqual {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  static constexpr bool Test(const L lhs, const R rhs) {
+    return DstRangeRelationToSrcRange<R>(lhs) !=
+               DstRangeRelationToSrcRange<L>(rhs) ||
+           static_cast<decltype(lhs + rhs)>(lhs) !=
+               static_cast<decltype(lhs + rhs)>(rhs);
+  }
+};
+
+// These perform the actual math operations on the CheckedNumerics.
+// Binary arithmetic operations.
+template <template <typename, typename> class C, typename L, typename R>
+constexpr bool SafeCompare(const L lhs, const R rhs) {
+  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+                "Types must be numeric.");
+  using Promotion = BigEnoughPromotion<L, R>;
+  using BigType = typename Promotion::type;
+  return Promotion::is_contained
+             // Force to a larger type for speed if both are contained.
+             ? C<BigType, BigType>::Test(
+                   static_cast<BigType>(static_cast<L>(lhs)),
+                   static_cast<BigType>(static_cast<R>(rhs)))
+             // Let the template functions figure it out for mixed types.
+             : C<L, R>::Test(lhs, rhs);
+}
+
+template <typename Dst, typename Src>
+constexpr bool IsMaxInRangeForNumericType() {
+  return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
+                                          std::numeric_limits<Src>::max());
+}
+
+template <typename Dst, typename Src>
+constexpr bool IsMinInRangeForNumericType() {
+  return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
+                                       std::numeric_limits<Src>::lowest());
+}
+
+template <typename Dst, typename Src>
+constexpr Dst CommonMax() {
+  return !IsMaxInRangeForNumericType<Dst, Src>()
+             ? Dst(std::numeric_limits<Dst>::max())
+             : Dst(std::numeric_limits<Src>::max());
+}
+
+template <typename Dst, typename Src>
+constexpr Dst CommonMin() {
+  return !IsMinInRangeForNumericType<Dst, Src>()
+             ? Dst(std::numeric_limits<Dst>::lowest())
+             : Dst(std::numeric_limits<Src>::lowest());
+}
+
+// This is a wrapper to generate return the max or min for a supplied type.
+// If the argument is false, the returned value is the maximum. If true the
+// returned value is the minimum.
+template <typename Dst, typename Src = Dst>
+constexpr Dst CommonMaxOrMin(bool is_min) {
+  return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
+}
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
diff --git a/src/base/numerics/safe_math.h b/src/base/numerics/safe_math.h
new file mode 100644 (file)
index 0000000..e30be90
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_SAFE_MATH_H_
+#define BASE_NUMERICS_SAFE_MATH_H_
+
+#include "base/numerics/checked_math.h"
+#include "base/numerics/clamped_math.h"
+#include "base/numerics/safe_conversions.h"
+
+#endif  // BASE_NUMERICS_SAFE_MATH_H_
diff --git a/src/base/numerics/safe_math_clang_gcc_impl.h b/src/base/numerics/safe_math_clang_gcc_impl.h
new file mode 100644 (file)
index 0000000..660a57f
--- /dev/null
@@ -0,0 +1,152 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
+#define BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
+
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+
+#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
+
+namespace base {
+namespace internal {
+
+// These are the non-functioning boilerplate implementations of the optimized
+// safe math routines.
+#if !BASE_HAS_ASSEMBLER_SAFE_MATH
+template <typename T, typename U>
+struct CheckedMulFastAsmOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr bool Do(T, U, V*) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<bool>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastAsmOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastAsmOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastAsmOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+#endif  // BASE_HAS_ASSEMBLER_SAFE_MATH
+#undef BASE_HAS_ASSEMBLER_SAFE_MATH
+
+template <typename T, typename U>
+struct CheckedAddFastOp {
+  static const bool is_supported = true;
+  template <typename V>
+  __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+    return !__builtin_add_overflow(x, y, result);
+  }
+};
+
+template <typename T, typename U>
+struct CheckedSubFastOp {
+  static const bool is_supported = true;
+  template <typename V>
+  __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+    return !__builtin_sub_overflow(x, y, result);
+  }
+};
+
+template <typename T, typename U>
+struct CheckedMulFastOp {
+#if defined(__clang__)
+  // TODO(jschuh): Get the Clang runtime library issues sorted out so we can
+  // support full-width, mixed-sign multiply builtins.
+  // https://crbug.com/613003
+  // We can support intptr_t, uintptr_t, or a smaller common type.
+  static const bool is_supported =
+      (IsTypeInRangeForNumericType<intptr_t, T>::value &&
+       IsTypeInRangeForNumericType<intptr_t, U>::value) ||
+      (IsTypeInRangeForNumericType<uintptr_t, T>::value &&
+       IsTypeInRangeForNumericType<uintptr_t, U>::value);
+#else
+  static const bool is_supported = true;
+#endif
+  template <typename V>
+  __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+    return CheckedMulFastAsmOp<T, U>::is_supported
+               ? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
+               : !__builtin_mul_overflow(x, y, result);
+  }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastOp {
+  static const bool is_supported = ClampedAddFastAsmOp<T, U>::is_supported;
+  template <typename V>
+  __attribute__((always_inline)) static V Do(T x, U y) {
+    return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
+  }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastOp {
+  static const bool is_supported = ClampedSubFastAsmOp<T, U>::is_supported;
+  template <typename V>
+  __attribute__((always_inline)) static V Do(T x, U y) {
+    return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
+  }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastOp {
+  static const bool is_supported = ClampedMulFastAsmOp<T, U>::is_supported;
+  template <typename V>
+  __attribute__((always_inline)) static V Do(T x, U y) {
+    return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
+  }
+};
+
+template <typename T>
+struct ClampedNegFastOp {
+  static const bool is_supported = std::is_signed<T>::value;
+  __attribute__((always_inline)) static T Do(T value) {
+    // Use this when there is no assembler path available.
+    if (!ClampedSubFastAsmOp<T, T>::is_supported) {
+      T result;
+      return !__builtin_sub_overflow(T(0), value, &result)
+                 ? result
+                 : std::numeric_limits<T>::max();
+    }
+
+    // Fallback to the normal subtraction path.
+    return ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
+  }
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
diff --git a/src/base/numerics/safe_math_shared_impl.h b/src/base/numerics/safe_math_shared_impl.h
new file mode 100644 (file)
index 0000000..6fe7b06
--- /dev/null
@@ -0,0 +1,236 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
+#define BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+
+// Where available use builtin math overflow support on Clang and GCC.
+#if ((defined(__clang__) &&                                \
+      ((__clang_major__ > 3) ||                            \
+       (__clang_major__ == 3 && __clang_minor__ >= 4))) || \
+     (defined(__GNUC__) && __GNUC__ >= 5))
+#include "base/numerics/safe_math_clang_gcc_impl.h"
+#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
+#else
+#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
+#endif
+
+namespace base {
+namespace internal {
+
+// These are the non-functioning boilerplate implementations of the optimized
+// safe math routines.
+#if !BASE_HAS_OPTIMIZED_SAFE_MATH
+template <typename T, typename U>
+struct CheckedAddFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr bool Do(T, U, V*) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<bool>();
+  }
+};
+
+template <typename T, typename U>
+struct CheckedSubFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr bool Do(T, U, V*) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<bool>();
+  }
+};
+
+template <typename T, typename U>
+struct CheckedMulFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr bool Do(T, U, V*) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<bool>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastOp {
+  static const bool is_supported = false;
+  template <typename V>
+  static constexpr V Do(T, U) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<V>();
+  }
+};
+
+template <typename T>
+struct ClampedNegFastOp {
+  static const bool is_supported = false;
+  static constexpr T Do(T) {
+    // Force a compile failure if instantiated.
+    return CheckOnFailure::template HandleFailure<T>();
+  }
+};
+#endif  // BASE_HAS_OPTIMIZED_SAFE_MATH
+#undef BASE_HAS_OPTIMIZED_SAFE_MATH
+
+// This is used for UnsignedAbs, where we need to support floating-point
+// template instantiations even though we don't actually support the operations.
+// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
+// so the float versions will not compile.
+template <typename Numeric,
+          bool IsInteger = std::is_integral<Numeric>::value,
+          bool IsFloat = std::is_floating_point<Numeric>::value>
+struct UnsignedOrFloatForSize;
+
+template <typename Numeric>
+struct UnsignedOrFloatForSize<Numeric, true, false> {
+  using type = typename std::make_unsigned<Numeric>::type;
+};
+
+template <typename Numeric>
+struct UnsignedOrFloatForSize<Numeric, false, true> {
+  using type = Numeric;
+};
+
+// Wrap the unary operations to allow SFINAE when instantiating integrals versus
+// floating points. These don't perform any overflow checking. Rather, they
+// exhibit well-defined overflow semantics and rely on the caller to detect
+// if an overflow occurred.
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T NegateWrapper(T value) {
+  using UnsignedT = typename std::make_unsigned<T>::type;
+  // This will compile to a NEG on Intel, and is normal negation on ARM.
+  return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
+}
+
+template <
+    typename T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T NegateWrapper(T value) {
+  return -value;
+}
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
+  return ~value;
+}
+
+template <typename T,
+          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T AbsWrapper(T value) {
+  return static_cast<T>(SafeUnsignedAbs(value));
+}
+
+template <
+    typename T,
+    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T AbsWrapper(T value) {
+  return value < 0 ? -value : value;
+}
+
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R>
+struct MathWrapper {
+  using math = M<typename UnderlyingType<L>::type,
+                 typename UnderlyingType<R>::type,
+                 void>;
+  using type = typename math::result_type;
+};
+
+// These variadic templates work out the return types.
+// TODO(jschuh): Rip all this out once we have C++14 non-trailing auto support.
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R,
+          typename... Args>
+struct ResultType;
+
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R>
+struct ResultType<M, L, R> {
+  using type = typename MathWrapper<M, L, R>::type;
+};
+
+template <template <typename, typename, typename> class M,
+          typename L,
+          typename R,
+          typename... Args>
+struct ResultType {
+  using type =
+      typename ResultType<M, typename ResultType<M, L, R>::type, Args...>::type;
+};
+
+// The following macros are just boilerplate for the standard arithmetic
+// operator overloads and variadic function templates. A macro isn't the nicest
+// solution, but it beats rewriting these over and over again.
+#define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)       \
+  template <typename L, typename R, typename... Args>                   \
+  constexpr CLASS##Numeric<                                             \
+      typename ResultType<CLASS##OP_NAME##Op, L, R, Args...>::type>     \
+      CL_ABBR##OP_NAME(const L lhs, const R rhs, const Args... args) {  \
+    return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
+                                                              args...); \
+  }
+
+#define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
+  /* Binary arithmetic operator for all CLASS##Numeric operations. */          \
+  template <typename L, typename R,                                            \
+            typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* =       \
+                nullptr>                                                       \
+  constexpr CLASS##Numeric<                                                    \
+      typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type>                    \
+  operator OP(const L lhs, const R rhs) {                                      \
+    return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs,      \
+                                                                     rhs);     \
+  }                                                                            \
+  /* Assignment arithmetic operator implementation from CLASS##Numeric. */     \
+  template <typename L>                                                        \
+  template <typename R>                                                        \
+  constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP(             \
+      const R rhs) {                                                           \
+    return MathOp<CLASS##OP_NAME##Op>(rhs);                                    \
+  }                                                                            \
+  /* Variadic arithmetic functions that return CLASS##Numeric. */              \
+  BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
diff --git a/src/base/posix/eintr_wrapper.h b/src/base/posix/eintr_wrapper.h
new file mode 100644 (file)
index 0000000..e18372e
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This provides a wrapper around system calls which may be interrupted by a
+// signal and return EINTR. See man 7 signal.
+// To prevent long-lasting loops (which would likely be a bug, such as a signal
+// that should be masked) to go unnoticed, there is a limit after which the
+// caller will nonetheless see an EINTR in Debug builds.
+//
+// On Windows and Fuchsia, this wrapper macro does nothing because there are no
+// signals.
+//
+// Don't wrap close calls in HANDLE_EINTR. Use IGNORE_EINTR if the return
+// value of close is significant. See http://crbug.com/269623.
+
+#ifndef BASE_POSIX_EINTR_WRAPPER_H_
+#define BASE_POSIX_EINTR_WRAPPER_H_
+
+#include "util/build_config.h"
+
+#if defined(OS_POSIX) && !defined(OS_FUCHSIA)
+
+#include <errno.h>
+
+#if defined(NDEBUG)
+
+#define HANDLE_EINTR(x)                                     \
+  ({                                                        \
+    decltype(x) eintr_wrapper_result;                       \
+    do {                                                    \
+      eintr_wrapper_result = (x);                           \
+    } while (eintr_wrapper_result == -1 && errno == EINTR); \
+    eintr_wrapper_result;                                   \
+  })
+
+#else
+
+#define HANDLE_EINTR(x)                                      \
+  ({                                                         \
+    int eintr_wrapper_counter = 0;                           \
+    decltype(x) eintr_wrapper_result;                        \
+    do {                                                     \
+      eintr_wrapper_result = (x);                            \
+    } while (eintr_wrapper_result == -1 && errno == EINTR && \
+             eintr_wrapper_counter++ < 100);                 \
+    eintr_wrapper_result;                                    \
+  })
+
+#endif  // NDEBUG
+
+#define IGNORE_EINTR(x)                                   \
+  ({                                                      \
+    decltype(x) eintr_wrapper_result;                     \
+    do {                                                  \
+      eintr_wrapper_result = (x);                         \
+      if (eintr_wrapper_result == -1 && errno == EINTR) { \
+        eintr_wrapper_result = 0;                         \
+      }                                                   \
+    } while (0);                                          \
+    eintr_wrapper_result;                                 \
+  })
+
+#else  // !OS_POSIX || OS_FUCHSIA
+
+#define HANDLE_EINTR(x) (x)
+#define IGNORE_EINTR(x) (x)
+
+#endif  // !OS_POSIX || OS_FUCHSIA
+
+#endif  // BASE_POSIX_EINTR_WRAPPER_H_
diff --git a/src/base/posix/file_descriptor_shuffle.cc b/src/base/posix/file_descriptor_shuffle.cc
new file mode 100644 (file)
index 0000000..f628bec
--- /dev/null
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/posix/file_descriptor_shuffle.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <ostream>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+
+namespace base {
+
+bool PerformInjectiveMultimapDestructive(InjectiveMultimap* m,
+                                         InjectionDelegate* delegate) {
+  static const size_t kMaxExtraFDs = 16;
+  int extra_fds[kMaxExtraFDs];
+  unsigned next_extra_fd = 0;
+
+  // DANGER: this function must not allocate or lock.
+  // Cannot use STL iterators here, since debug iterators use locks.
+
+  for (size_t i_index = 0; i_index < m->size(); ++i_index) {
+    InjectiveMultimap::value_type* i = &(*m)[i_index];
+    int temp_fd = -1;
+
+    // We DCHECK the injectiveness of the mapping.
+    for (size_t j_index = i_index + 1; j_index < m->size(); ++j_index) {
+      InjectiveMultimap::value_type* j = &(*m)[j_index];
+      DCHECK(i->dest != j->dest) << "Both fd " << i->source << " and "
+                                 << j->source << " map to " << i->dest;
+    }
+
+    const bool is_identity = i->source == i->dest;
+
+    for (size_t j_index = i_index + 1; j_index < m->size(); ++j_index) {
+      InjectiveMultimap::value_type* j = &(*m)[j_index];
+      if (!is_identity && i->dest == j->source) {
+        if (temp_fd == -1) {
+          if (!delegate->Duplicate(&temp_fd, i->dest))
+            return false;
+          if (next_extra_fd < kMaxExtraFDs) {
+            extra_fds[next_extra_fd++] = temp_fd;
+          } else {
+            RAW_LOG(ERROR,
+                    "PerformInjectiveMultimapDestructive overflowed "
+                    "extra_fds. Leaking file descriptors!");
+          }
+        }
+
+        j->source = temp_fd;
+        j->close = false;
+      }
+
+      if (i->close && i->source == j->dest)
+        i->close = false;
+
+      if (i->close && i->source == j->source) {
+        i->close = false;
+        j->close = true;
+      }
+    }
+
+    if (!is_identity) {
+      if (!delegate->Move(i->source, i->dest))
+        return false;
+    }
+
+    if (!is_identity && i->close)
+      delegate->Close(i->source);
+  }
+
+  for (unsigned i = 0; i < next_extra_fd; i++)
+    delegate->Close(extra_fds[i]);
+
+  return true;
+}
+
+bool PerformInjectiveMultimap(const InjectiveMultimap& m_in,
+                              InjectionDelegate* delegate) {
+  InjectiveMultimap m(m_in);
+  return PerformInjectiveMultimapDestructive(&m, delegate);
+}
+
+bool FileDescriptorTableInjection::Duplicate(int* result, int fd) {
+  *result = HANDLE_EINTR(dup(fd));
+  return *result >= 0;
+}
+
+bool FileDescriptorTableInjection::Move(int src, int dest) {
+  return HANDLE_EINTR(dup2(src, dest)) != -1;
+}
+
+void FileDescriptorTableInjection::Close(int fd) {
+  int ret = IGNORE_EINTR(close(fd));
+  DPCHECK(ret == 0);
+}
+
+}  // namespace base
diff --git a/src/base/posix/file_descriptor_shuffle.h b/src/base/posix/file_descriptor_shuffle.h
new file mode 100644 (file)
index 0000000..6843498
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_
+#define BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_
+
+// This code exists to shuffle file descriptors, which is commonly needed when
+// forking subprocesses. The naive approach (just call dup2 to set up the
+// desired descriptors) is very simple, but wrong: it won't handle edge cases
+// (like mapping 0 -> 1, 1 -> 0) correctly.
+//
+// In order to unittest this code, it's broken into the abstract action (an
+// injective multimap) and the concrete code for dealing with file descriptors.
+// Users should use the code like this:
+//   base::InjectiveMultimap file_descriptor_map;
+//   file_descriptor_map.push_back(base::InjectionArc(devnull, 0, true));
+//   file_descriptor_map.push_back(base::InjectionArc(devnull, 2, true));
+//   file_descriptor_map.push_back(base::InjectionArc(pipe[1], 1, true));
+//   base::ShuffleFileDescriptors(file_descriptor_map);
+//
+// and trust the the Right Thing will get done.
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+
+namespace base {
+
+// A Delegate which performs the actions required to perform an injective
+// multimapping in place.
+class InjectionDelegate {
+ public:
+  // Duplicate |fd|, an element of the domain, and write a fresh element of the
+  // domain into |result|. Returns true iff successful.
+  virtual bool Duplicate(int* result, int fd) = 0;
+  // Destructively move |src| to |dest|, overwriting |dest|. Returns true iff
+  // successful.
+  virtual bool Move(int src, int dest) = 0;
+  // Delete an element of the domain.
+  virtual void Close(int fd) = 0;
+
+ protected:
+  virtual ~InjectionDelegate() = default;
+};
+
+// An implementation of the InjectionDelegate interface using the file
+// descriptor table of the current process as the domain.
+class FileDescriptorTableInjection : public InjectionDelegate {
+  bool Duplicate(int* result, int fd) override;
+  bool Move(int src, int dest) override;
+  void Close(int fd) override;
+};
+
+// A single arc of the directed graph which describes an injective multimapping.
+struct InjectionArc {
+  InjectionArc(int in_source, int in_dest, bool in_close)
+      : source(in_source), dest(in_dest), close(in_close) {}
+
+  int source;
+  int dest;
+  bool close;  // if true, delete the source element after performing the
+               // mapping.
+};
+
+typedef std::vector<InjectionArc> InjectiveMultimap;
+
+bool PerformInjectiveMultimap(const InjectiveMultimap& map,
+                              InjectionDelegate* delegate);
+
+bool PerformInjectiveMultimapDestructive(InjectiveMultimap* map,
+                                         InjectionDelegate* delegate);
+
+// This function will not call malloc but will mutate |map|
+static inline bool ShuffleFileDescriptors(InjectiveMultimap* map) {
+  FileDescriptorTableInjection delegate;
+  return PerformInjectiveMultimapDestructive(map, &delegate);
+}
+
+}  // namespace base
+
+#endif  // BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_
diff --git a/src/base/posix/safe_strerror.cc b/src/base/posix/safe_strerror.cc
new file mode 100644 (file)
index 0000000..252b5df
--- /dev/null
@@ -0,0 +1,126 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if defined(__ANDROID__)
+// Post-L versions of bionic define the GNU-specific strerror_r if _GNU_SOURCE
+// is defined, but the symbol is renamed to __gnu_strerror_r which only exists
+// on those later versions. To preserve ABI compatibility with older versions,
+// undefine _GNU_SOURCE and use the POSIX version.
+#undef _GNU_SOURCE
+#endif
+
+#include "base/posix/safe_strerror.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "util/build_config.h"
+
+namespace base {
+
+#if defined(__GLIBC__)
+#define USE_HISTORICAL_STRERRO_R 1
+#else
+#define USE_HISTORICAL_STRERRO_R 0
+#endif
+
+#if USE_HISTORICAL_STRERRO_R && defined(__GNUC__)
+// GCC will complain about the unused second wrap function unless we tell it
+// that we meant for them to be potentially unused, which is exactly what this
+// attribute is for.
+#define POSSIBLY_UNUSED __attribute__((unused))
+#else
+#define POSSIBLY_UNUSED
+#endif
+
+#if USE_HISTORICAL_STRERRO_R
+// glibc has two strerror_r functions: a historical GNU-specific one that
+// returns type char *, and a POSIX.1-2001 compliant one available since 2.3.4
+// that returns int. This wraps the GNU-specific one.
+static void POSSIBLY_UNUSED
+wrap_posix_strerror_r(char* (*strerror_r_ptr)(int, char*, size_t),
+                      int err,
+                      char* buf,
+                      size_t len) {
+  // GNU version.
+  char* rc = (*strerror_r_ptr)(err, buf, len);
+  if (rc != buf) {
+    // glibc did not use buf and returned a static string instead. Copy it
+    // into buf.
+    buf[0] = '\0';
+    strncat(buf, rc, len - 1);
+  }
+  // The GNU version never fails. Unknown errors get an "unknown error" message.
+  // The result is always null terminated.
+}
+#endif  // USE_HISTORICAL_STRERRO_R
+
+// Wrapper for strerror_r functions that implement the POSIX interface. POSIX
+// does not define the behaviour for some of the edge cases, so we wrap it to
+// guarantee that they are handled. This is compiled on all POSIX platforms, but
+// it will only be used on Linux if the POSIX strerror_r implementation is
+// being used (see below).
+static void POSSIBLY_UNUSED wrap_posix_strerror_r(int (*strerror_r_ptr)(int,
+                                                                        char*,
+                                                                        size_t),
+                                                  int err,
+                                                  char* buf,
+                                                  size_t len) {
+  int old_errno = errno;
+  // Have to cast since otherwise we get an error if this is the GNU version
+  // (but in such a scenario this function is never called). Sadly we can't use
+  // C++-style casts because the appropriate one is reinterpret_cast but it's
+  // considered illegal to reinterpret_cast a type to itself, so we get an
+  // error in the opposite case.
+  int result = (*strerror_r_ptr)(err, buf, len);
+  if (result == 0) {
+    // POSIX is vague about whether the string will be terminated, although
+    // it indirectly implies that typically ERANGE will be returned, instead
+    // of truncating the string. We play it safe by always terminating the
+    // string explicitly.
+    buf[len - 1] = '\0';
+  } else {
+    // Error. POSIX is vague about whether the return value is itself a system
+    // error code or something else. On Linux currently it is -1 and errno is
+    // set. On BSD-derived systems it is a system error and errno is unchanged.
+    // We try and detect which case it is so as to put as much useful info as
+    // we can into our message.
+    int strerror_error;  // The error encountered in strerror
+    int new_errno = errno;
+    if (new_errno != old_errno) {
+      // errno was changed, so probably the return value is just -1 or something
+      // else that doesn't provide any info, and errno is the error.
+      strerror_error = new_errno;
+    } else {
+      // Either the error from strerror_r was the same as the previous value, or
+      // errno wasn't used. Assume the latter.
+      strerror_error = result;
+    }
+    // snprintf truncates and always null-terminates.
+    snprintf(buf, len, "Error %d while retrieving error %d", strerror_error,
+             err);
+  }
+  errno = old_errno;
+}
+
+void safe_strerror_r(int err, char* buf, size_t len) {
+  if (buf == nullptr || len <= 0) {
+    return;
+  }
+  // If using glibc (i.e., Linux), the compiler will automatically select the
+  // appropriate overloaded function based on the function type of strerror_r.
+  // The other one will be elided from the translation unit since both are
+  // static.
+  wrap_posix_strerror_r(&strerror_r, err, buf, len);
+}
+
+std::string safe_strerror(int err) {
+  const int buffer_size = 256;
+  char buf[buffer_size];
+  safe_strerror_r(err, buf, sizeof(buf));
+  return std::string(buf);
+}
+
+}  // namespace base
diff --git a/src/base/posix/safe_strerror.h b/src/base/posix/safe_strerror.h
new file mode 100644 (file)
index 0000000..48e9db9
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_POSIX_SAFE_STRERROR_H_
+#define BASE_POSIX_SAFE_STRERROR_H_
+
+#include <stddef.h>
+
+#include <string>
+
+namespace base {
+
+// BEFORE using anything from this file, first look at PLOG and friends in
+// logging.h and use them instead if applicable.
+//
+// This file declares safe, portable alternatives to the POSIX strerror()
+// function. strerror() is inherently unsafe in multi-threaded apps and should
+// never be used. Doing so can cause crashes. Additionally, the thread-safe
+// alternative strerror_r varies in semantics across platforms. Use these
+// functions instead.
+
+// Thread-safe strerror function with dependable semantics that never fails.
+// It will write the string form of error "err" to buffer buf of length len.
+// If there is an error calling the OS's strerror_r() function then a message to
+// that effect will be printed into buf, truncating if necessary. The final
+// result is always null-terminated. The value of errno is never changed.
+//
+// Use this instead of strerror_r().
+void safe_strerror_r(int err, char* buf, size_t len);
+
+// Calls safe_strerror_r with a buffer of suitable size and returns the result
+// in a C++ string.
+//
+// Use this instead of strerror(). Note though that safe_strerror_r will be
+// more robust in the case of heap corruption errors, since it doesn't need to
+// allocate a string.
+std::string safe_strerror(int err);
+
+}  // namespace base
+
+#endif  // BASE_POSIX_SAFE_STRERROR_H_
diff --git a/src/base/scoped_clear_errno.h b/src/base/scoped_clear_errno.h
new file mode 100644 (file)
index 0000000..44a0c62
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SCOPED_CLEAR_ERRNO_H_
+#define BASE_SCOPED_CLEAR_ERRNO_H_
+
+#include <errno.h>
+
+#include "base/macros.h"
+
+namespace base {
+
+// Simple scoper that saves the current value of errno, resets it to 0, and on
+// destruction puts the old value back.
+class ScopedClearErrno {
+ public:
+  ScopedClearErrno() : old_errno_(errno) { errno = 0; }
+  ~ScopedClearErrno() {
+    if (errno == 0)
+      errno = old_errno_;
+  }
+
+ private:
+  const int old_errno_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedClearErrno);
+};
+
+}  // namespace base
+
+#endif  // BASE_SCOPED_CLEAR_ERRNO_H_
diff --git a/src/base/scoped_generic.h b/src/base/scoped_generic.h
new file mode 100644 (file)
index 0000000..b1d479d
--- /dev/null
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SCOPED_GENERIC_H_
+#define BASE_SCOPED_GENERIC_H_
+
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+
+namespace base {
+
+// This class acts like unique_ptr with a custom deleter (although is slightly
+// less fancy in some of the more escoteric respects) except that it keeps a
+// copy of the object rather than a pointer, and we require that the contained
+// object has some kind of "invalid" value.
+//
+// Defining a scoper based on this class allows you to get a scoper for
+// non-pointer types without having to write custom code for set, reset, and
+// move, etc. and get almost identical semantics that people are used to from
+// unique_ptr.
+//
+// It is intended that you will typedef this class with an appropriate deleter
+// to implement clean up tasks for objects that act like pointers from a
+// resource management standpoint but aren't, such as file descriptors and
+// various types of operating system handles. Using unique_ptr for these
+// things requires that you keep a pointer to the handle valid for the lifetime
+// of the scoper (which is easy to mess up).
+//
+// For an object to be able to be put into a ScopedGeneric, it must support
+// standard copyable semantics and have a specific "invalid" value. The traits
+// must define a free function and also the invalid value to assign for
+// default-constructed and released objects.
+//
+//   struct FooScopedTraits {
+//     // It's assumed that this is a fast inline function with little-to-no
+//     // penalty for duplicate calls. This must be a static function even
+//     // for stateful traits.
+//     static int InvalidValue() {
+//       return 0;
+//     }
+//
+//     // This free function will not be called if f == InvalidValue()!
+//     static void Free(int f) {
+//       ::FreeFoo(f);
+//     }
+//   };
+//
+//   typedef ScopedGeneric<int, FooScopedTraits> ScopedFoo;
+template <typename T, typename Traits>
+class ScopedGeneric {
+ private:
+  // This must be first since it's used inline below.
+  //
+  // Use the empty base class optimization to allow us to have a D
+  // member, while avoiding any space overhead for it when D is an
+  // empty class.  See e.g. http://www.cantrip.org/emptyopt.html for a good
+  // discussion of this technique.
+  struct Data : public Traits {
+    explicit Data(const T& in) : generic(in) {}
+    Data(const T& in, const Traits& other) : Traits(other), generic(in) {}
+    T generic;
+  };
+
+ public:
+  typedef T element_type;
+  typedef Traits traits_type;
+
+  ScopedGeneric() : data_(traits_type::InvalidValue()) {}
+
+  // Constructor. Takes responsibility for freeing the resource associated with
+  // the object T.
+  explicit ScopedGeneric(const element_type& value) : data_(value) {}
+
+  // Constructor. Allows initialization of a stateful traits object.
+  ScopedGeneric(const element_type& value, const traits_type& traits)
+      : data_(value, traits) {}
+
+  // Move constructor. Allows initialization from a ScopedGeneric rvalue.
+  ScopedGeneric(ScopedGeneric<T, Traits>&& rvalue)
+      : data_(rvalue.release(), rvalue.get_traits()) {}
+
+  ~ScopedGeneric() { FreeIfNecessary(); }
+
+  // operator=. Allows assignment from a ScopedGeneric rvalue.
+  ScopedGeneric& operator=(ScopedGeneric<T, Traits>&& rvalue) {
+    reset(rvalue.release());
+    return *this;
+  }
+
+  // Frees the currently owned object, if any. Then takes ownership of a new
+  // object, if given. Self-resets are not allowed as on unique_ptr. See
+  // http://crbug.com/162971
+  void reset(const element_type& value = traits_type::InvalidValue()) {
+    if (data_.generic != traits_type::InvalidValue() && data_.generic == value)
+      abort();
+    FreeIfNecessary();
+    data_.generic = value;
+  }
+
+  void swap(ScopedGeneric& other) {
+    // Standard swap idiom: 'using std::swap' ensures that std::swap is
+    // present in the overload set, but we call swap unqualified so that
+    // any more-specific overloads can be used, if available.
+    using std::swap;
+    swap(static_cast<Traits&>(data_), static_cast<Traits&>(other.data_));
+    swap(data_.generic, other.data_.generic);
+  }
+
+  // Release the object. The return value is the current object held by this
+  // object. After this operation, this object will hold a null value, and
+  // will not own the object any more.
+  element_type release() WARN_UNUSED_RESULT {
+    element_type old_generic = data_.generic;
+    data_.generic = traits_type::InvalidValue();
+    return old_generic;
+  }
+
+  // Returns a raw pointer to the object storage, to allow the scoper to be used
+  // to receive and manage out-parameter values. Implies reset().
+  element_type* receive() WARN_UNUSED_RESULT {
+    reset();
+    return &data_.generic;
+  }
+
+  const element_type& get() const { return data_.generic; }
+
+  // Returns true if this object doesn't hold the special null value for the
+  // associated data type.
+  bool is_valid() const { return data_.generic != traits_type::InvalidValue(); }
+
+  bool operator==(const element_type& value) const {
+    return data_.generic == value;
+  }
+  bool operator!=(const element_type& value) const {
+    return data_.generic != value;
+  }
+
+  Traits& get_traits() { return data_; }
+  const Traits& get_traits() const { return data_; }
+
+ private:
+  void FreeIfNecessary() {
+    if (data_.generic != traits_type::InvalidValue()) {
+      data_.Free(data_.generic);
+      data_.generic = traits_type::InvalidValue();
+    }
+  }
+
+  // Forbid comparison. If U != T, it totally doesn't make sense, and if U ==
+  // T, it still doesn't make sense because you should never have the same
+  // object owned by two different ScopedGenerics.
+  template <typename T2, typename Traits2>
+  bool operator==(const ScopedGeneric<T2, Traits2>& p2) const;
+  template <typename T2, typename Traits2>
+  bool operator!=(const ScopedGeneric<T2, Traits2>& p2) const;
+
+  Data data_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedGeneric);
+};
+
+template <class T, class Traits>
+void swap(const ScopedGeneric<T, Traits>& a,
+          const ScopedGeneric<T, Traits>& b) {
+  a.swap(b);
+}
+
+template <class T, class Traits>
+bool operator==(const T& value, const ScopedGeneric<T, Traits>& scoped) {
+  return value == scoped.get();
+}
+
+template <class T, class Traits>
+bool operator!=(const T& value, const ScopedGeneric<T, Traits>& scoped) {
+  return value != scoped.get();
+}
+
+}  // namespace base
+
+#endif  // BASE_SCOPED_GENERIC_H_
diff --git a/src/base/sha1.cc b/src/base/sha1.cc
new file mode 100644 (file)
index 0000000..9c22be1
--- /dev/null
@@ -0,0 +1,213 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/sha1.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "base/sys_byteorder.h"
+
+namespace base {
+
+// Implementation of SHA-1. Only handles data in byte-sized blocks,
+// which simplifies the code a fair bit.
+
+// Identifier names follow notation in FIPS PUB 180-3, where you'll
+// also find a description of the algorithm:
+// http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf
+
+// Usage example:
+//
+// SecureHashAlgorithm sha;
+// while(there is data to hash)
+//   sha.Update(moredata, size of data);
+// sha.Final();
+// memcpy(somewhere, sha.Digest(), 20);
+//
+// to reuse the instance of sha, call sha.Init();
+
+// TODO(jhawkins): Replace this implementation with a per-platform
+// implementation using each platform's crypto library.  See
+// http://crbug.com/47218
+
+class SecureHashAlgorithm {
+ public:
+  SecureHashAlgorithm() { Init(); }
+
+  static const int kDigestSizeBytes;
+
+  void Init();
+  void Update(const void* data, size_t nbytes);
+  void Final();
+
+  // 20 bytes of message digest.
+  const unsigned char* Digest() const {
+    return reinterpret_cast<const unsigned char*>(H);
+  }
+
+ private:
+  void Pad();
+  void Process();
+
+  uint32_t A, B, C, D, E;
+
+  uint32_t H[5];
+
+  union {
+    uint32_t W[80];
+    uint8_t M[64];
+  };
+
+  uint32_t cursor;
+  uint64_t l;
+};
+
+static inline uint32_t f(uint32_t t, uint32_t B, uint32_t C, uint32_t D) {
+  if (t < 20) {
+    return (B & C) | ((~B) & D);
+  } else if (t < 40) {
+    return B ^ C ^ D;
+  } else if (t < 60) {
+    return (B & C) | (B & D) | (C & D);
+  } else {
+    return B ^ C ^ D;
+  }
+}
+
+static inline uint32_t S(uint32_t n, uint32_t X) {
+  return (X << n) | (X >> (32 - n));
+}
+
+static inline uint32_t K(uint32_t t) {
+  if (t < 20) {
+    return 0x5a827999;
+  } else if (t < 40) {
+    return 0x6ed9eba1;
+  } else if (t < 60) {
+    return 0x8f1bbcdc;
+  } else {
+    return 0xca62c1d6;
+  }
+}
+
+const int SecureHashAlgorithm::kDigestSizeBytes = 20;
+
+void SecureHashAlgorithm::Init() {
+  A = 0;
+  B = 0;
+  C = 0;
+  D = 0;
+  E = 0;
+  cursor = 0;
+  l = 0;
+  H[0] = 0x67452301;
+  H[1] = 0xefcdab89;
+  H[2] = 0x98badcfe;
+  H[3] = 0x10325476;
+  H[4] = 0xc3d2e1f0;
+}
+
+void SecureHashAlgorithm::Final() {
+  Pad();
+  Process();
+
+  for (int t = 0; t < 5; ++t)
+    H[t] = ByteSwap(H[t]);
+}
+
+void SecureHashAlgorithm::Update(const void* data, size_t nbytes) {
+  const uint8_t* d = reinterpret_cast<const uint8_t*>(data);
+  while (nbytes--) {
+    M[cursor++] = *d++;
+    if (cursor >= 64)
+      Process();
+    l += 8;
+  }
+}
+
+void SecureHashAlgorithm::Pad() {
+  M[cursor++] = 0x80;
+
+  if (cursor > 64 - 8) {
+    // pad out to next block
+    while (cursor < 64)
+      M[cursor++] = 0;
+
+    Process();
+  }
+
+  while (cursor < 64 - 8)
+    M[cursor++] = 0;
+
+  M[cursor++] = (l >> 56) & 0xff;
+  M[cursor++] = (l >> 48) & 0xff;
+  M[cursor++] = (l >> 40) & 0xff;
+  M[cursor++] = (l >> 32) & 0xff;
+  M[cursor++] = (l >> 24) & 0xff;
+  M[cursor++] = (l >> 16) & 0xff;
+  M[cursor++] = (l >> 8) & 0xff;
+  M[cursor++] = l & 0xff;
+}
+
+void SecureHashAlgorithm::Process() {
+  uint32_t t;
+
+  // Each a...e corresponds to a section in the FIPS 180-3 algorithm.
+
+  // a.
+  //
+  // W and M are in a union, so no need to memcpy.
+  // memcpy(W, M, sizeof(M));
+  for (t = 0; t < 16; ++t)
+    W[t] = ByteSwap(W[t]);
+
+  // b.
+  for (t = 16; t < 80; ++t)
+    W[t] = S(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]);
+
+  // c.
+  A = H[0];
+  B = H[1];
+  C = H[2];
+  D = H[3];
+  E = H[4];
+
+  // d.
+  for (t = 0; t < 80; ++t) {
+    uint32_t TEMP = S(5, A) + f(t, B, C, D) + E + W[t] + K(t);
+    E = D;
+    D = C;
+    C = S(30, B);
+    B = A;
+    A = TEMP;
+  }
+
+  // e.
+  H[0] += A;
+  H[1] += B;
+  H[2] += C;
+  H[3] += D;
+  H[4] += E;
+
+  cursor = 0;
+}
+
+std::string SHA1HashString(const std::string& str) {
+  char hash[SecureHashAlgorithm::kDigestSizeBytes];
+  SHA1HashBytes(reinterpret_cast<const unsigned char*>(str.c_str()),
+                str.length(), reinterpret_cast<unsigned char*>(hash));
+  return std::string(hash, SecureHashAlgorithm::kDigestSizeBytes);
+}
+
+void SHA1HashBytes(const unsigned char* data, size_t len, unsigned char* hash) {
+  SecureHashAlgorithm sha;
+  sha.Update(data, len);
+  sha.Final();
+
+  memcpy(hash, sha.Digest(), SecureHashAlgorithm::kDigestSizeBytes);
+}
+
+}  // namespace base
diff --git a/src/base/sha1.h b/src/base/sha1.h
new file mode 100644 (file)
index 0000000..482ebe6
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SHA1_H_
+#define BASE_SHA1_H_
+
+#include <stddef.h>
+
+#include <string>
+
+namespace base {
+
+// These functions perform SHA-1 operations.
+
+static const size_t kSHA1Length = 20;  // Length in bytes of a SHA-1 hash.
+
+// Computes the SHA-1 hash of the input string |str| and returns the full
+// hash.
+std::string SHA1HashString(const std::string& str);
+
+// Computes the SHA-1 hash of the |len| bytes in |data| and puts the hash
+// in |hash|. |hash| must be kSHA1Length bytes long.
+void SHA1HashBytes(const unsigned char* data, size_t len, unsigned char* hash);
+
+}  // namespace base
+
+#endif  // BASE_SHA1_H_
diff --git a/src/base/stl_util.h b/src/base/stl_util.h
new file mode 100644 (file)
index 0000000..d08c018
--- /dev/null
@@ -0,0 +1,272 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Derived from google3/util/gtl/stl_util.h
+
+#ifndef BASE_STL_UTIL_H_
+#define BASE_STL_UTIL_H_
+
+#include <algorithm>
+#include <deque>
+#include <forward_list>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "base/logging.h"
+
+namespace base {
+
+namespace internal {
+
+// Calls erase on iterators of matching elements.
+template <typename Container, typename Predicate>
+void IterateAndEraseIf(Container& container, Predicate pred) {
+  for (auto it = container.begin(); it != container.end();) {
+    if (pred(*it))
+      it = container.erase(it);
+    else
+      ++it;
+  }
+}
+
+}  // namespace internal
+
+// Test to see if a set or map contains a particular key.
+// Returns true if the key is in the collection.
+template <typename Collection, typename Key>
+bool ContainsKey(const Collection& collection, const Key& key) {
+  return collection.find(key) != collection.end();
+}
+
+namespace internal {
+
+template <typename Collection>
+class HasKeyType {
+  template <typename C>
+  static std::true_type test(typename C::key_type*);
+  template <typename C>
+  static std::false_type test(...);
+
+ public:
+  static constexpr bool value = decltype(test<Collection>(nullptr))::value;
+};
+
+}  // namespace internal
+
+// Test to see if a collection like a vector contains a particular value.
+// Returns true if the value is in the collection.
+// Don't use this on collections such as sets or maps. This is enforced by
+// disabling this method if the collection defines a key_type.
+template <typename Collection,
+          typename Value,
+          typename std::enable_if<!internal::HasKeyType<Collection>::value,
+                                  int>::type = 0>
+bool ContainsValue(const Collection& collection, const Value& value) {
+  return std::find(std::begin(collection), std::end(collection), value) !=
+         std::end(collection);
+}
+
+// Returns true if the container is sorted.
+template <typename Container>
+bool STLIsSorted(const Container& cont) {
+  // Note: Use reverse iterator on container to ensure we only require
+  // value_type to implement operator<.
+  return std::adjacent_find(cont.rbegin(), cont.rend(),
+                            std::less<typename Container::value_type>()) ==
+         cont.rend();
+}
+
+// Returns a new ResultType containing the difference of two sorted containers.
+template <typename ResultType, typename Arg1, typename Arg2>
+ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) {
+  DCHECK(STLIsSorted(a1));
+  DCHECK(STLIsSorted(a2));
+  ResultType difference;
+  std::set_difference(a1.begin(), a1.end(), a2.begin(), a2.end(),
+                      std::inserter(difference, difference.end()));
+  return difference;
+}
+
+// Returns a new ResultType containing the union of two sorted containers.
+template <typename ResultType, typename Arg1, typename Arg2>
+ResultType STLSetUnion(const Arg1& a1, const Arg2& a2) {
+  DCHECK(STLIsSorted(a1));
+  DCHECK(STLIsSorted(a2));
+  ResultType result;
+  std::set_union(a1.begin(), a1.end(), a2.begin(), a2.end(),
+                 std::inserter(result, result.end()));
+  return result;
+}
+
+// Returns a new ResultType containing the intersection of two sorted
+// containers.
+template <typename ResultType, typename Arg1, typename Arg2>
+ResultType STLSetIntersection(const Arg1& a1, const Arg2& a2) {
+  DCHECK(STLIsSorted(a1));
+  DCHECK(STLIsSorted(a2));
+  ResultType result;
+  std::set_intersection(a1.begin(), a1.end(), a2.begin(), a2.end(),
+                        std::inserter(result, result.end()));
+  return result;
+}
+
+// Returns true if the sorted container |a1| contains all elements of the sorted
+// container |a2|.
+template <typename Arg1, typename Arg2>
+bool STLIncludes(const Arg1& a1, const Arg2& a2) {
+  DCHECK(STLIsSorted(a1));
+  DCHECK(STLIsSorted(a2));
+  return std::includes(a1.begin(), a1.end(), a2.begin(), a2.end());
+}
+
+// Erase/EraseIf are based on library fundamentals ts v2 erase/erase_if
+// http://en.cppreference.com/w/cpp/experimental/lib_extensions_2
+// They provide a generic way to erase elements from a container.
+// The functions here implement these for the standard containers until those
+// functions are available in the C++ standard.
+// For Chromium containers overloads should be defined in their own headers
+// (like standard containers).
+// Note: there is no std::erase for standard associative containers so we don't
+// have it either.
+
+template <typename CharT, typename Traits, typename Allocator, typename Value>
+void Erase(std::basic_string<CharT, Traits, Allocator>& container,
+           const Value& value) {
+  container.erase(std::remove(container.begin(), container.end(), value),
+                  container.end());
+}
+
+template <typename CharT, typename Traits, typename Allocator, class Predicate>
+void EraseIf(std::basic_string<CharT, Traits, Allocator>& container,
+             Predicate pred) {
+  container.erase(std::remove_if(container.begin(), container.end(), pred),
+                  container.end());
+}
+
+template <class T, class Allocator, class Value>
+void Erase(std::deque<T, Allocator>& container, const Value& value) {
+  container.erase(std::remove(container.begin(), container.end(), value),
+                  container.end());
+}
+
+template <class T, class Allocator, class Predicate>
+void EraseIf(std::deque<T, Allocator>& container, Predicate pred) {
+  container.erase(std::remove_if(container.begin(), container.end(), pred),
+                  container.end());
+}
+
+template <class T, class Allocator, class Value>
+void Erase(std::vector<T, Allocator>& container, const Value& value) {
+  container.erase(std::remove(container.begin(), container.end(), value),
+                  container.end());
+}
+
+template <class T, class Allocator, class Predicate>
+void EraseIf(std::vector<T, Allocator>& container, Predicate pred) {
+  container.erase(std::remove_if(container.begin(), container.end(), pred),
+                  container.end());
+}
+
+template <class T, class Allocator, class Value>
+void Erase(std::forward_list<T, Allocator>& container, const Value& value) {
+  // Unlike std::forward_list::remove, this function template accepts
+  // heterogeneous types and does not force a conversion to the container's
+  // value type before invoking the == operator.
+  container.remove_if([&](const T& cur) { return cur == value; });
+}
+
+template <class T, class Allocator, class Predicate>
+void EraseIf(std::forward_list<T, Allocator>& container, Predicate pred) {
+  container.remove_if(pred);
+}
+
+template <class T, class Allocator, class Value>
+void Erase(std::list<T, Allocator>& container, const Value& value) {
+  // Unlike std::list::remove, this function template accepts heterogeneous
+  // types and does not force a conversion to the container's value type before
+  // invoking the == operator.
+  container.remove_if([&](const T& cur) { return cur == value; });
+}
+
+template <class T, class Allocator, class Predicate>
+void EraseIf(std::list<T, Allocator>& container, Predicate pred) {
+  container.remove_if(pred);
+}
+
+template <class Key, class T, class Compare, class Allocator, class Predicate>
+void EraseIf(std::map<Key, T, Compare, Allocator>& container, Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key, class T, class Compare, class Allocator, class Predicate>
+void EraseIf(std::multimap<Key, T, Compare, Allocator>& container,
+             Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key, class Compare, class Allocator, class Predicate>
+void EraseIf(std::set<Key, Compare, Allocator>& container, Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key, class Compare, class Allocator, class Predicate>
+void EraseIf(std::multiset<Key, Compare, Allocator>& container,
+             Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key,
+          class T,
+          class Hash,
+          class KeyEqual,
+          class Allocator,
+          class Predicate>
+void EraseIf(std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& container,
+             Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key,
+          class T,
+          class Hash,
+          class KeyEqual,
+          class Allocator,
+          class Predicate>
+void EraseIf(
+    std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>& container,
+    Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key,
+          class Hash,
+          class KeyEqual,
+          class Allocator,
+          class Predicate>
+void EraseIf(std::unordered_set<Key, Hash, KeyEqual, Allocator>& container,
+             Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+template <class Key,
+          class Hash,
+          class KeyEqual,
+          class Allocator,
+          class Predicate>
+void EraseIf(std::unordered_multiset<Key, Hash, KeyEqual, Allocator>& container,
+             Predicate pred) {
+  internal::IterateAndEraseIf(container, pred);
+}
+
+}  // namespace base
+
+#endif  // BASE_STL_UTIL_H_
diff --git a/src/base/strings/string_number_conversions.cc b/src/base/strings/string_number_conversions.cc
new file mode 100644 (file)
index 0000000..9156fa3
--- /dev/null
@@ -0,0 +1,458 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_number_conversions.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <wctype.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/logging.h"
+#include "base/numerics/safe_math.h"
+#include "base/scoped_clear_errno.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace base {
+
+namespace {
+
+template <typename STR, typename INT>
+struct IntToStringT {
+  static STR IntToString(INT value) {
+    // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
+    // So round up to allocate 3 output characters per byte, plus 1 for '-'.
+    const size_t kOutputBufSize =
+        3 * sizeof(INT) + std::numeric_limits<INT>::is_signed;
+
+    // Create the string in a temporary buffer, write it back to front, and
+    // then return the substr of what we ended up using.
+    using CHR = typename STR::value_type;
+    CHR outbuf[kOutputBufSize];
+
+    // The ValueOrDie call below can never fail, because UnsignedAbs is valid
+    // for all valid inputs.
+    typename std::make_unsigned<INT>::type res =
+        CheckedNumeric<INT>(value).UnsignedAbs().ValueOrDie();
+
+    CHR* end = outbuf + kOutputBufSize;
+    CHR* i = end;
+    do {
+      --i;
+      DCHECK(i != outbuf);
+      *i = static_cast<CHR>((res % 10) + '0');
+      res /= 10;
+    } while (res != 0);
+    if (IsValueNegative(value)) {
+      --i;
+      DCHECK(i != outbuf);
+      *i = static_cast<CHR>('-');
+    }
+    return STR(i, end);
+  }
+};
+
+// Utility to convert a character to a digit in a given base
+template <typename CHAR, int BASE, bool BASE_LTE_10>
+class BaseCharToDigit {};
+
+// Faster specialization for bases <= 10
+template <typename CHAR, int BASE>
+class BaseCharToDigit<CHAR, BASE, true> {
+ public:
+  static bool Convert(CHAR c, uint8_t* digit) {
+    if (c >= '0' && c < '0' + BASE) {
+      *digit = static_cast<uint8_t>(c - '0');
+      return true;
+    }
+    return false;
+  }
+};
+
+// Specialization for bases where 10 < base <= 36
+template <typename CHAR, int BASE>
+class BaseCharToDigit<CHAR, BASE, false> {
+ public:
+  static bool Convert(CHAR c, uint8_t* digit) {
+    if (c >= '0' && c <= '9') {
+      *digit = c - '0';
+    } else if (c >= 'a' && c < 'a' + BASE - 10) {
+      *digit = c - 'a' + 10;
+    } else if (c >= 'A' && c < 'A' + BASE - 10) {
+      *digit = c - 'A' + 10;
+    } else {
+      return false;
+    }
+    return true;
+  }
+};
+
+template <int BASE, typename CHAR>
+bool CharToDigit(CHAR c, uint8_t* digit) {
+  return BaseCharToDigit<CHAR, BASE, BASE <= 10>::Convert(c, digit);
+}
+
+// There is an IsUnicodeWhitespace for wchars defined in string_util.h, but it
+// is locale independent, whereas the functions we are replacing were
+// locale-dependent. TBD what is desired, but for the moment let's not
+// introduce a change in behaviour.
+template <typename CHAR>
+class WhitespaceHelper {};
+
+template <>
+class WhitespaceHelper<char> {
+ public:
+  static bool Invoke(char c) {
+    return 0 != isspace(static_cast<unsigned char>(c));
+  }
+};
+
+template <>
+class WhitespaceHelper<char16_t> {
+ public:
+  static bool Invoke(char16_t c) { return 0 != iswspace(c); }
+};
+
+template <typename CHAR>
+bool LocalIsWhitespace(CHAR c) {
+  return WhitespaceHelper<CHAR>::Invoke(c);
+}
+
+// IteratorRangeToNumberTraits should provide:
+//  - a typedef for iterator_type, the iterator type used as input.
+//  - a typedef for value_type, the target numeric type.
+//  - static functions min, max (returning the minimum and maximum permitted
+//    values)
+//  - constant kBase, the base in which to interpret the input
+template <typename IteratorRangeToNumberTraits>
+class IteratorRangeToNumber {
+ public:
+  typedef IteratorRangeToNumberTraits traits;
+  typedef typename traits::iterator_type const_iterator;
+  typedef typename traits::value_type value_type;
+
+  // Generalized iterator-range-to-number conversion.
+  //
+  static bool Invoke(const_iterator begin,
+                     const_iterator end,
+                     value_type* output) {
+    bool valid = true;
+
+    while (begin != end && LocalIsWhitespace(*begin)) {
+      valid = false;
+      ++begin;
+    }
+
+    if (begin != end && *begin == '-') {
+      if (!std::numeric_limits<value_type>::is_signed) {
+        *output = 0;
+        valid = false;
+      } else if (!Negative::Invoke(begin + 1, end, output)) {
+        valid = false;
+      }
+    } else {
+      if (begin != end && *begin == '+') {
+        ++begin;
+      }
+      if (!Positive::Invoke(begin, end, output)) {
+        valid = false;
+      }
+    }
+
+    return valid;
+  }
+
+ private:
+  // Sign provides:
+  //  - a static function, CheckBounds, that determines whether the next digit
+  //    causes an overflow/underflow
+  //  - a static function, Increment, that appends the next digit appropriately
+  //    according to the sign of the number being parsed.
+  template <typename Sign>
+  class Base {
+   public:
+    static bool Invoke(const_iterator begin,
+                       const_iterator end,
+                       typename traits::value_type* output) {
+      *output = 0;
+
+      if (begin == end) {
+        return false;
+      }
+
+      // Note: no performance difference was found when using template
+      // specialization to remove this check in bases other than 16
+      if (traits::kBase == 16 && end - begin > 2 && *begin == '0' &&
+          (*(begin + 1) == 'x' || *(begin + 1) == 'X')) {
+        begin += 2;
+      }
+
+      for (const_iterator current = begin; current != end; ++current) {
+        uint8_t new_digit = 0;
+
+        if (!CharToDigit<traits::kBase>(*current, &new_digit)) {
+          return false;
+        }
+
+        if (current != begin) {
+          if (!Sign::CheckBounds(output, new_digit)) {
+            return false;
+          }
+          *output *= traits::kBase;
+        }
+
+        Sign::Increment(new_digit, output);
+      }
+      return true;
+    }
+  };
+
+  class Positive : public Base<Positive> {
+   public:
+    static bool CheckBounds(value_type* output, uint8_t new_digit) {
+      if (*output > static_cast<value_type>(traits::max() / traits::kBase) ||
+          (*output == static_cast<value_type>(traits::max() / traits::kBase) &&
+           new_digit > traits::max() % traits::kBase)) {
+        *output = traits::max();
+        return false;
+      }
+      return true;
+    }
+    static void Increment(uint8_t increment, value_type* output) {
+      *output += increment;
+    }
+  };
+
+  class Negative : public Base<Negative> {
+   public:
+    static bool CheckBounds(value_type* output, uint8_t new_digit) {
+      if (*output < traits::min() / traits::kBase ||
+          (*output == traits::min() / traits::kBase &&
+           new_digit > 0 - traits::min() % traits::kBase)) {
+        *output = traits::min();
+        return false;
+      }
+      return true;
+    }
+    static void Increment(uint8_t increment, value_type* output) {
+      *output -= increment;
+    }
+  };
+};
+
+template <typename ITERATOR, typename VALUE, int BASE>
+class BaseIteratorRangeToNumberTraits {
+ public:
+  typedef ITERATOR iterator_type;
+  typedef VALUE value_type;
+  static value_type min() { return std::numeric_limits<value_type>::min(); }
+  static value_type max() { return std::numeric_limits<value_type>::max(); }
+  static const int kBase = BASE;
+};
+
+template <typename ITERATOR>
+class BaseHexIteratorRangeToIntTraits
+    : public BaseIteratorRangeToNumberTraits<ITERATOR, int, 16> {};
+
+template <typename ITERATOR>
+class BaseHexIteratorRangeToUIntTraits
+    : public BaseIteratorRangeToNumberTraits<ITERATOR, uint32_t, 16> {};
+
+template <typename ITERATOR>
+class BaseHexIteratorRangeToInt64Traits
+    : public BaseIteratorRangeToNumberTraits<ITERATOR, int64_t, 16> {};
+
+template <typename ITERATOR>
+class BaseHexIteratorRangeToUInt64Traits
+    : public BaseIteratorRangeToNumberTraits<ITERATOR, uint64_t, 16> {};
+
+typedef BaseHexIteratorRangeToIntTraits<std::string_view::const_iterator>
+    HexIteratorRangeToIntTraits;
+
+typedef BaseHexIteratorRangeToUIntTraits<std::string_view::const_iterator>
+    HexIteratorRangeToUIntTraits;
+
+typedef BaseHexIteratorRangeToInt64Traits<std::string_view::const_iterator>
+    HexIteratorRangeToInt64Traits;
+
+typedef BaseHexIteratorRangeToUInt64Traits<std::string_view::const_iterator>
+    HexIteratorRangeToUInt64Traits;
+
+template <typename VALUE, int BASE>
+class StringPieceToNumberTraits
+    : public BaseIteratorRangeToNumberTraits<std::string_view::const_iterator,
+                                             VALUE,
+                                             BASE> {};
+
+template <typename VALUE>
+bool StringToIntImpl(std::string_view input, VALUE* output) {
+  return IteratorRangeToNumber<StringPieceToNumberTraits<VALUE, 10>>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+template <typename VALUE, int BASE>
+class StringPiece16ToNumberTraits : public BaseIteratorRangeToNumberTraits<
+                                        std::u16string_view::const_iterator,
+                                        VALUE,
+                                        BASE> {};
+
+template <typename VALUE>
+bool String16ToIntImpl(std::u16string_view input, VALUE* output) {
+  return IteratorRangeToNumber<StringPiece16ToNumberTraits<VALUE, 10>>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+}  // namespace
+
+std::string NumberToString(int value) {
+  return IntToStringT<std::string, int>::IntToString(value);
+}
+
+std::u16string NumberToString16(int value) {
+  return IntToStringT<std::u16string, int>::IntToString(value);
+}
+
+std::string NumberToString(unsigned value) {
+  return IntToStringT<std::string, unsigned>::IntToString(value);
+}
+
+std::u16string NumberToString16(unsigned value) {
+  return IntToStringT<std::u16string, unsigned>::IntToString(value);
+}
+
+std::string NumberToString(long value) {
+  return IntToStringT<std::string, long>::IntToString(value);
+}
+
+std::u16string NumberToString16(long value) {
+  return IntToStringT<std::u16string, long>::IntToString(value);
+}
+
+std::string NumberToString(unsigned long value) {
+  return IntToStringT<std::string, unsigned long>::IntToString(value);
+}
+
+std::u16string NumberToString16(unsigned long value) {
+  return IntToStringT<std::u16string, unsigned long>::IntToString(value);
+}
+
+std::string NumberToString(long long value) {
+  return IntToStringT<std::string, long long>::IntToString(value);
+}
+
+std::u16string NumberToString16(long long value) {
+  return IntToStringT<std::u16string, long long>::IntToString(value);
+}
+
+std::string NumberToString(unsigned long long value) {
+  return IntToStringT<std::string, unsigned long long>::IntToString(value);
+}
+
+std::u16string NumberToString16(unsigned long long value) {
+  return IntToStringT<std::u16string, unsigned long long>::IntToString(value);
+}
+
+bool StringToInt(std::string_view input, int* output) {
+  return StringToIntImpl(input, output);
+}
+
+bool StringToInt(std::u16string_view input, int* output) {
+  return String16ToIntImpl(input, output);
+}
+
+bool StringToUint(std::string_view input, unsigned* output) {
+  return StringToIntImpl(input, output);
+}
+
+bool StringToUint(std::u16string_view input, unsigned* output) {
+  return String16ToIntImpl(input, output);
+}
+
+bool StringToInt64(std::string_view input, int64_t* output) {
+  return StringToIntImpl(input, output);
+}
+
+bool StringToInt64(std::u16string_view input, int64_t* output) {
+  return String16ToIntImpl(input, output);
+}
+
+bool StringToUint64(std::string_view input, uint64_t* output) {
+  return StringToIntImpl(input, output);
+}
+
+bool StringToUint64(std::u16string_view input, uint64_t* output) {
+  return String16ToIntImpl(input, output);
+}
+
+bool StringToSizeT(std::string_view input, size_t* output) {
+  return StringToIntImpl(input, output);
+}
+
+bool StringToSizeT(std::u16string_view input, size_t* output) {
+  return String16ToIntImpl(input, output);
+}
+
+// Note: if you need to add String16ToDouble, first ask yourself if it's
+// really necessary. If it is, probably the best implementation here is to
+// convert to 8-bit and then use the 8-bit version.
+
+// Note: if you need to add an iterator range version of StringToDouble, first
+// ask yourself if it's really necessary. If it is, probably the best
+// implementation here is to instantiate a string and use the string version.
+
+std::string HexEncode(const void* bytes, size_t size) {
+  static const char kHexChars[] = "0123456789ABCDEF";
+
+  // Each input byte creates two output hex characters.
+  std::string ret(size * 2, '\0');
+
+  for (size_t i = 0; i < size; ++i) {
+    char b = reinterpret_cast<const char*>(bytes)[i];
+    ret[(i * 2)] = kHexChars[(b >> 4) & 0xf];
+    ret[(i * 2) + 1] = kHexChars[b & 0xf];
+  }
+  return ret;
+}
+
+bool HexStringToInt(std::string_view input, int* output) {
+  return IteratorRangeToNumber<HexIteratorRangeToIntTraits>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+bool HexStringToUInt(std::string_view input, uint32_t* output) {
+  return IteratorRangeToNumber<HexIteratorRangeToUIntTraits>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+bool HexStringToInt64(std::string_view input, int64_t* output) {
+  return IteratorRangeToNumber<HexIteratorRangeToInt64Traits>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+bool HexStringToUInt64(std::string_view input, uint64_t* output) {
+  return IteratorRangeToNumber<HexIteratorRangeToUInt64Traits>::Invoke(
+      input.begin(), input.end(), output);
+}
+
+bool HexStringToBytes(std::string_view input, std::vector<uint8_t>* output) {
+  DCHECK_EQ(output->size(), 0u);
+  size_t count = input.size();
+  if (count == 0 || (count % 2) != 0)
+    return false;
+  for (uintptr_t i = 0; i < count / 2; ++i) {
+    uint8_t msb = 0;  // most significant 4 bits
+    uint8_t lsb = 0;  // least significant 4 bits
+    if (!CharToDigit<16>(input[i * 2], &msb) ||
+        !CharToDigit<16>(input[i * 2 + 1], &lsb)) {
+      return false;
+    }
+    output->push_back((msb << 4) | lsb);
+  }
+  return true;
+}
+
+}  // namespace base
diff --git a/src/base/strings/string_number_conversions.h b/src/base/strings/string_number_conversions.h
new file mode 100644 (file)
index 0000000..81360a0
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_
+#define BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "util/build_config.h"
+
+namespace base {
+
+// Number -> string conversions ------------------------------------------------
+
+// Ignores locale! see warning above.
+std::string NumberToString(int value);
+std::u16string NumberToString16(int value);
+std::string NumberToString(unsigned int value);
+std::u16string NumberToString16(unsigned int value);
+std::string NumberToString(long value);
+std::u16string NumberToString16(long value);
+std::string NumberToString(unsigned long value);
+std::u16string NumberToString16(unsigned long value);
+std::string NumberToString(long long value);
+std::u16string NumberToString16(long long value);
+std::string NumberToString(unsigned long long value);
+std::u16string NumberToString16(unsigned long long value);
+
+// Type-specific naming for backwards compatibility.
+//
+// TODO(brettw) these should be removed and callers converted to the overloaded
+// "NumberToString" variant.
+inline std::string IntToString(int value) {
+  return NumberToString(value);
+}
+inline std::u16string IntToString16(int value) {
+  return NumberToString16(value);
+}
+inline std::string UintToString(unsigned value) {
+  return NumberToString(value);
+}
+inline std::u16string UintToString16(unsigned value) {
+  return NumberToString16(value);
+}
+inline std::string Int64ToString(int64_t value) {
+  return NumberToString(value);
+}
+inline std::u16string Int64ToString16(int64_t value) {
+  return NumberToString16(value);
+}
+
+// String -> number conversions ------------------------------------------------
+
+// Perform a best-effort conversion of the input string to a numeric type,
+// setting |*output| to the result of the conversion.  Returns true for
+// "perfect" conversions; returns false in the following cases:
+//  - Overflow. |*output| will be set to the maximum value supported
+//    by the data type.
+//  - Underflow. |*output| will be set to the minimum value supported
+//    by the data type.
+//  - Trailing characters in the string after parsing the number.  |*output|
+//    will be set to the value of the number that was parsed.
+//  - Leading whitespace in the string before parsing the number. |*output| will
+//    be set to the value of the number that was parsed.
+//  - No characters parseable as a number at the beginning of the string.
+//    |*output| will be set to 0.
+//  - Empty string.  |*output| will be set to 0.
+// WARNING: Will write to |output| even when returning false.
+//          Read the comments above carefully.
+bool StringToInt(std::string_view input, int* output);
+bool StringToInt(std::u16string_view input, int* output);
+
+bool StringToUint(std::string_view input, unsigned* output);
+bool StringToUint(std::u16string_view input, unsigned* output);
+
+bool StringToInt64(std::string_view input, int64_t* output);
+bool StringToInt64(std::u16string_view input, int64_t* output);
+
+bool StringToUint64(std::string_view input, uint64_t* output);
+bool StringToUint64(std::u16string_view input, uint64_t* output);
+
+bool StringToSizeT(std::string_view input, size_t* output);
+bool StringToSizeT(std::u16string_view input, size_t* output);
+
+// Hex encoding ----------------------------------------------------------------
+
+// Returns a hex string representation of a binary buffer. The returned hex
+// string will be in upper case. This function does not check if |size| is
+// within reasonable limits since it's written with trusted data in mind.  If
+// you suspect that the data you want to format might be large, the absolute
+// max size for |size| should be is
+//   std::numeric_limits<size_t>::max() / 2
+std::string HexEncode(const void* bytes, size_t size);
+
+// Best effort conversion, see StringToInt above for restrictions.
+// Will only successful parse hex values that will fit into |output|, i.e.
+// -0x80000000 < |input| < 0x7FFFFFFF.
+bool HexStringToInt(std::string_view input, int* output);
+
+// Best effort conversion, see StringToInt above for restrictions.
+// Will only successful parse hex values that will fit into |output|, i.e.
+// 0x00000000 < |input| < 0xFFFFFFFF.
+// The string is not required to start with 0x.
+bool HexStringToUInt(std::string_view input, uint32_t* output);
+
+// Best effort conversion, see StringToInt above for restrictions.
+// Will only successful parse hex values that will fit into |output|, i.e.
+// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF.
+bool HexStringToInt64(std::string_view input, int64_t* output);
+
+// Best effort conversion, see StringToInt above for restrictions.
+// Will only successful parse hex values that will fit into |output|, i.e.
+// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF.
+// The string is not required to start with 0x.
+bool HexStringToUInt64(std::string_view input, uint64_t* output);
+
+// Similar to the previous functions, except that output is a vector of bytes.
+// |*output| will contain as many bytes as were successfully parsed prior to the
+// error.  There is no overflow, but input.size() must be evenly divisible by 2.
+// Leading 0x or +/- are not allowed.
+bool HexStringToBytes(std::string_view input, std::vector<uint8_t>* output);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_NUMBER_CONVERSIONS_H_
diff --git a/src/base/strings/string_split.cc b/src/base/strings/string_split.cc
new file mode 100644 (file)
index 0000000..f4c7eb2
--- /dev/null
@@ -0,0 +1,260 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_split.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/third_party/icu/icu_utf.h"
+
+namespace base {
+
+namespace {
+
+// Returns either the ASCII or UTF-16 whitespace.
+template <typename char_type>
+std::basic_string_view<char_type> WhitespaceForType();
+template <>
+std::u16string_view WhitespaceForType<char16_t>() {
+  return kWhitespaceUTF16;
+}
+template <>
+std::string_view WhitespaceForType<char>() {
+  return kWhitespaceASCII;
+}
+
+// Optimize the single-character case to call find() on the string instead,
+// since this is the common case and can be made faster. This could have been
+// done with template specialization too, but would have been less clear.
+//
+// There is no corresponding FindFirstNotOf because std::string_view already
+// implements these different versions that do the optimized searching.
+size_t FindFirstOf(std::string_view piece, char c, size_t pos) {
+  return piece.find(c, pos);
+}
+size_t FindFirstOf(std::u16string_view piece, char16_t c, size_t pos) {
+  return piece.find(c, pos);
+}
+size_t FindFirstOf(std::string_view piece,
+                   std::string_view one_of,
+                   size_t pos) {
+  return piece.find_first_of(one_of, pos);
+}
+size_t FindFirstOf(std::u16string_view piece,
+                   std::u16string_view one_of,
+                   size_t pos) {
+  return piece.find_first_of(one_of, pos);
+}
+
+// General string splitter template. Can take 8- or 16-bit input, can produce
+// the corresponding string or std::string_view output, and can take single- or
+// multiple-character delimiters.
+//
+// DelimiterType is either a character (Str::value_type) or a string piece of
+// multiple characters (std::basic_string_view<char>). std::string_view has a
+// version of find for both of these cases, and the single-character version is
+// the most common and can be implemented faster, which is why this is a
+// template.
+template <typename char_type, typename OutputStringType, typename DelimiterType>
+static std::vector<OutputStringType> SplitStringT(
+    std::basic_string_view<char_type> str,
+    DelimiterType delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  std::vector<OutputStringType> result;
+  if (str.empty())
+    return result;
+
+  using ViewType = std::basic_string_view<char_type>;
+
+  size_t start = 0;
+  while (start != ViewType::npos) {
+    size_t end = FindFirstOf(str, delimiter, start);
+
+    ViewType piece;
+    if (end == ViewType::npos) {
+      piece = str.substr(start);
+      start = ViewType::npos;
+    } else {
+      piece = str.substr(start, end - start);
+      start = end + 1;
+    }
+
+    if (whitespace == TRIM_WHITESPACE)
+      piece = TrimString(piece, WhitespaceForType<char_type>(), TRIM_ALL);
+
+    if (result_type == SPLIT_WANT_ALL || !piece.empty())
+      result.emplace_back(piece);
+  }
+  return result;
+}
+
+bool AppendStringKeyValue(std::string_view input,
+                          char delimiter,
+                          StringPairs* result) {
+  // Always append a new item regardless of success (it might be empty). The
+  // below code will copy the strings directly into the result pair.
+  result->resize(result->size() + 1);
+  auto& result_pair = result->back();
+
+  // Find the delimiter.
+  size_t end_key_pos = input.find_first_of(delimiter);
+  if (end_key_pos == std::string::npos) {
+    return false;  // No delimiter.
+  }
+  result_pair.first.assign(input.substr(0, end_key_pos));
+
+  // Find the value string.
+  std::string_view remains =
+      input.substr(end_key_pos, input.size() - end_key_pos);
+  size_t begin_value_pos = remains.find_first_not_of(delimiter);
+  if (begin_value_pos == std::string_view::npos) {
+    return false;  // No value.
+  }
+  result_pair.second.assign(
+      remains.substr(begin_value_pos, remains.size() - begin_value_pos));
+
+  return true;
+}
+
+template <typename char_type, typename OutputStringType>
+void SplitStringUsingSubstrT(std::basic_string_view<char_type> input,
+                             std::basic_string_view<char_type> delimiter,
+                             WhitespaceHandling whitespace,
+                             SplitResult result_type,
+                             std::vector<OutputStringType>* result) {
+  using Piece = std::basic_string_view<char_type>;
+  using size_type = typename Piece::size_type;
+
+  result->clear();
+  for (size_type begin_index = 0, end_index = 0; end_index != Piece::npos;
+       begin_index = end_index + delimiter.size()) {
+    end_index = input.find(delimiter, begin_index);
+    Piece term = end_index == Piece::npos
+                     ? input.substr(begin_index)
+                     : input.substr(begin_index, end_index - begin_index);
+
+    if (whitespace == TRIM_WHITESPACE)
+      term = TrimString(term, WhitespaceForType<char_type>(), TRIM_ALL);
+
+    if (result_type == SPLIT_WANT_ALL || !term.empty())
+      result->emplace_back(term);
+  }
+}
+
+}  // namespace
+
+std::vector<std::string> SplitString(std::string_view input,
+                                     std::string_view separators,
+                                     WhitespaceHandling whitespace,
+                                     SplitResult result_type) {
+  if (separators.size() == 1) {
+    return SplitStringT<char, std::string, char>(input, separators[0],
+                                                 whitespace, result_type);
+  }
+  return SplitStringT<char, std::string, std::string_view>(
+      input, separators, whitespace, result_type);
+}
+
+std::vector<std::u16string> SplitString(std::u16string_view input,
+                                        std::u16string_view separators,
+                                        WhitespaceHandling whitespace,
+                                        SplitResult result_type) {
+  if (separators.size() == 1) {
+    return SplitStringT<char16_t, std::u16string, char16_t>(
+        input, separators[0], whitespace, result_type);
+  }
+  return SplitStringT<char16_t, std::u16string, std::u16string_view>(
+      input, separators, whitespace, result_type);
+}
+
+std::vector<std::string_view> SplitStringPiece(std::string_view input,
+                                               std::string_view separators,
+                                               WhitespaceHandling whitespace,
+                                               SplitResult result_type) {
+  if (separators.size() == 1) {
+    return SplitStringT<char, std::string_view, char>(input, separators[0],
+                                                      whitespace, result_type);
+  }
+  return SplitStringT<char, std::string_view, std::string_view>(
+      input, separators, whitespace, result_type);
+}
+
+std::vector<std::u16string_view> SplitStringPiece(
+    std::u16string_view input,
+    std::u16string_view separators,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  if (separators.size() == 1) {
+    return SplitStringT<char16_t, std::u16string_view, char16_t>(
+        input, separators[0], whitespace, result_type);
+  }
+  return SplitStringT<char16_t, std::u16string_view, std::u16string_view>(
+      input, separators, whitespace, result_type);
+}
+
+bool SplitStringIntoKeyValuePairs(std::string_view input,
+                                  char key_value_delimiter,
+                                  char key_value_pair_delimiter,
+                                  StringPairs* key_value_pairs) {
+  key_value_pairs->clear();
+
+  std::vector<std::string_view> pairs =
+      SplitStringPiece(input, std::string(1, key_value_pair_delimiter),
+                       TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+  key_value_pairs->reserve(pairs.size());
+
+  bool success = true;
+  for (const std::string_view& pair : pairs) {
+    if (!AppendStringKeyValue(pair, key_value_delimiter, key_value_pairs)) {
+      // Don't return here, to allow for pairs without associated
+      // value or key; just record that the split failed.
+      success = false;
+    }
+  }
+  return success;
+}
+
+std::vector<std::u16string> SplitStringUsingSubstr(
+    std::u16string_view input,
+    std::u16string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  std::vector<std::u16string> result;
+  SplitStringUsingSubstrT(input, delimiter, whitespace, result_type, &result);
+  return result;
+}
+
+std::vector<std::string> SplitStringUsingSubstr(std::string_view input,
+                                                std::string_view delimiter,
+                                                WhitespaceHandling whitespace,
+                                                SplitResult result_type) {
+  std::vector<std::string> result;
+  SplitStringUsingSubstrT(input, delimiter, whitespace, result_type, &result);
+  return result;
+}
+
+std::vector<std::u16string_view> SplitStringPieceUsingSubstr(
+    std::u16string_view input,
+    std::u16string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  std::vector<std::u16string_view> result;
+  SplitStringUsingSubstrT(input, delimiter, whitespace, result_type, &result);
+  return result;
+}
+
+std::vector<std::string_view> SplitStringPieceUsingSubstr(
+    std::string_view input,
+    std::string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  std::vector<std::string_view> result;
+  SplitStringUsingSubstrT(input, delimiter, whitespace, result_type, &result);
+  return result;
+}
+
+}  // namespace base
diff --git a/src/base/strings/string_split.h b/src/base/strings/string_split.h
new file mode 100644 (file)
index 0000000..7ba14e1
--- /dev/null
@@ -0,0 +1,122 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_SPLIT_H_
+#define BASE_STRINGS_STRING_SPLIT_H_
+
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace base {
+
+enum WhitespaceHandling {
+  KEEP_WHITESPACE,
+  TRIM_WHITESPACE,
+};
+
+enum SplitResult {
+  // Strictly return all results.
+  //
+  // If the input is ",," and the separator is ',' this will return a
+  // vector of three empty strings.
+  SPLIT_WANT_ALL,
+
+  // Only nonempty results will be added to the results. Multiple separators
+  // will be coalesced. Separators at the beginning and end of the input will
+  // be ignored. With TRIM_WHITESPACE, whitespace-only results will be dropped.
+  //
+  // If the input is ",," and the separator is ',', this will return an empty
+  // vector.
+  SPLIT_WANT_NONEMPTY,
+};
+
+// Split the given string on ANY of the given separators, returning copies of
+// the result.
+//
+// To split on either commas or semicolons, keeping all whitespace:
+//
+//   std::vector<std::string> tokens = base::SplitString(
+//       input, ",;", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+std::vector<std::string> SplitString(std::string_view input,
+                                     std::string_view separators,
+                                     WhitespaceHandling whitespace,
+                                     SplitResult result_type);
+std::vector<std::u16string> SplitString(std::u16string_view input,
+                                        std::u16string_view separators,
+                                        WhitespaceHandling whitespace,
+                                        SplitResult result_type);
+
+// Like SplitString above except it returns a vector of StringPieces which
+// reference the original buffer without copying. Although you have to be
+// careful to keep the original string unmodified, this provides an efficient
+// way to iterate through tokens in a string.
+//
+// To iterate through all whitespace-separated tokens in an input string:
+//
+//   for (const auto& cur :
+//        base::SplitStringPiece(input, base::kWhitespaceASCII,
+//                               base::KEEP_WHITESPACE,
+//                               base::SPLIT_WANT_NONEMPTY)) {
+//     ...
+std::vector<std::string_view> SplitStringPiece(std::string_view input,
+                                               std::string_view separators,
+                                               WhitespaceHandling whitespace,
+                                               SplitResult result_type);
+std::vector<std::u16string_view> SplitStringPiece(
+    std::u16string_view input,
+    std::u16string_view separators,
+    WhitespaceHandling whitespace,
+    SplitResult result_type);
+
+using StringPairs = std::vector<std::pair<std::string, std::string>>;
+
+// Splits |line| into key value pairs according to the given delimiters and
+// removes whitespace leading each key and trailing each value. Returns true
+// only if each pair has a non-empty key and value. |key_value_pairs| will
+// include ("","") pairs for entries without |key_value_delimiter|.
+bool SplitStringIntoKeyValuePairs(std::string_view input,
+                                  char key_value_delimiter,
+                                  char key_value_pair_delimiter,
+                                  StringPairs* key_value_pairs);
+
+// Similar to SplitString, but use a substring delimiter instead of a list of
+// characters that are all possible delimiters.
+std::vector<std::u16string> SplitStringUsingSubstr(
+    std::u16string_view input,
+    std::u16string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type);
+std::vector<std::string> SplitStringUsingSubstr(std::string_view input,
+                                                std::string_view delimiter,
+                                                WhitespaceHandling whitespace,
+                                                SplitResult result_type);
+
+// Like SplitStringUsingSubstr above except it returns a vector of StringPieces
+// which reference the original buffer without copying. Although you have to be
+// careful to keep the original string unmodified, this provides an efficient
+// way to iterate through tokens in a string.
+//
+// To iterate through all newline-separated tokens in an input string:
+//
+//   for (const auto& cur :
+//        base::SplitStringUsingSubstr(input, "\r\n",
+//                                     base::KEEP_WHITESPACE,
+//                                     base::SPLIT_WANT_NONEMPTY)) {
+//     ...
+std::vector<std::u16string_view> SplitStringPieceUsingSubstr(
+    std::u16string_view input,
+    std::u16string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type);
+std::vector<std::string_view> SplitStringPieceUsingSubstr(
+    std::string_view input,
+    std::string_view delimiter,
+    WhitespaceHandling whitespace,
+    SplitResult result_type);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_SPLIT_H_
diff --git a/src/base/strings/string_tokenizer.h b/src/base/strings/string_tokenizer.h
new file mode 100644 (file)
index 0000000..fe9401d
--- /dev/null
@@ -0,0 +1,250 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_TOKENIZER_H_
+#define BASE_STRINGS_STRING_TOKENIZER_H_
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+namespace base {
+
+// StringTokenizerT is a simple string tokenizer class.  It works like an
+// iterator that with each step (see the Advance method) updates members that
+// refer to the next token in the input string.  The user may optionally
+// configure the tokenizer to return delimiters.
+//
+// EXAMPLE 1:
+//
+//   char input[] = "this is a test";
+//   CStringTokenizer t(input, input + strlen(input), " ");
+//   while (t.GetNext()) {
+//     printf("%s\n", t.token().c_str());
+//   }
+//
+// Output:
+//
+//   this
+//   is
+//   a
+//   test
+//
+//
+// EXAMPLE 2:
+//
+//   std::string input = "no-cache=\"foo, bar\", private";
+//   StringTokenizer t(input, ", ");
+//   t.set_quote_chars("\"");
+//   while (t.GetNext()) {
+//     printf("%s\n", t.token().c_str());
+//   }
+//
+// Output:
+//
+//   no-cache="foo, bar"
+//   private
+//
+//
+// EXAMPLE 3:
+//
+//   bool next_is_option = false, next_is_value = false;
+//   std::string input = "text/html; charset=UTF-8; foo=bar";
+//   StringTokenizer t(input, "; =");
+//   t.set_options(StringTokenizer::RETURN_DELIMS);
+//   while (t.GetNext()) {
+//     if (t.token_is_delim()) {
+//       switch (*t.token_begin()) {
+//         case ';':
+//           next_is_option = true;
+//           break;
+//         case '=':
+//           next_is_value = true;
+//           break;
+//       }
+//     } else {
+//       const char* label;
+//       if (next_is_option) {
+//         label = "option-name";
+//         next_is_option = false;
+//       } else if (next_is_value) {
+//         label = "option-value";
+//         next_is_value = false;
+//       } else {
+//         label = "mime-type";
+//       }
+//       printf("%s: %s\n", label, t.token().c_str());
+//     }
+//   }
+//
+//
+template <class str, class const_iterator>
+class StringTokenizerT {
+ public:
+  typedef typename str::value_type char_type;
+
+  // Options that may be pass to set_options()
+  enum {
+    // Specifies the delimiters should be returned as tokens
+    RETURN_DELIMS = 1 << 0,
+  };
+
+  // The string object must live longer than the tokenizer. In particular, this
+  // should not be constructed with a temporary. The deleted rvalue constructor
+  // blocks the most obvious instances of this (e.g. passing a string literal to
+  // the constructor), but caution must still be exercised.
+  StringTokenizerT(const str& string, const str& delims) {
+    Init(string.begin(), string.end(), delims);
+  }
+
+  // Don't allow temporary strings to be used with string tokenizer, since
+  // Init() would otherwise save iterators to a temporary string.
+  StringTokenizerT(str&&, const str& delims) = delete;
+
+  StringTokenizerT(const_iterator string_begin,
+                   const_iterator string_end,
+                   const str& delims) {
+    Init(string_begin, string_end, delims);
+  }
+
+  // Set the options for this tokenizer.  By default, this is 0.
+  void set_options(int options) { options_ = options; }
+
+  // Set the characters to regard as quotes.  By default, this is empty.  When
+  // a quote char is encountered, the tokenizer will switch into a mode where
+  // it ignores delimiters that it finds.  It switches out of this mode once it
+  // finds another instance of the quote char.  If a backslash is encountered
+  // within a quoted string, then the next character is skipped.
+  void set_quote_chars(const str& quotes) { quotes_ = quotes; }
+
+  // Call this method to advance the tokenizer to the next delimiter.  This
+  // returns false if the tokenizer is complete.  This method must be called
+  // before calling any of the token* methods.
+  bool GetNext() {
+    if (quotes_.empty() && options_ == 0)
+      return QuickGetNext();
+    else
+      return FullGetNext();
+  }
+
+  // Start iterating through tokens from the beginning of the string.
+  void Reset() { token_end_ = start_pos_; }
+
+  // Returns true if token is a delimiter.  When the tokenizer is constructed
+  // with the RETURN_DELIMS option, this method can be used to check if the
+  // returned token is actually a delimiter.
+  bool token_is_delim() const { return token_is_delim_; }
+
+  // If GetNext() returned true, then these methods may be used to read the
+  // value of the token.
+  const_iterator token_begin() const { return token_begin_; }
+  const_iterator token_end() const { return token_end_; }
+  str token() const { return str(token_begin_, token_end_); }
+  std::basic_string_view<typename str::value_type> token_piece() const {
+    return std::basic_string_view<typename str::value_type>(
+        &*token_begin_, std::distance(token_begin_, token_end_));
+  }
+
+ private:
+  void Init(const_iterator string_begin,
+            const_iterator string_end,
+            const str& delims) {
+    start_pos_ = string_begin;
+    token_begin_ = string_begin;
+    token_end_ = string_begin;
+    end_ = string_end;
+    delims_ = delims;
+    options_ = 0;
+    token_is_delim_ = false;
+  }
+
+  // Implementation of GetNext() for when we have no quote characters. We have
+  // two separate implementations because AdvanceOne() is a hot spot in large
+  // text files with large tokens.
+  bool QuickGetNext() {
+    token_is_delim_ = false;
+    for (;;) {
+      token_begin_ = token_end_;
+      if (token_end_ == end_)
+        return false;
+      ++token_end_;
+      if (delims_.find(*token_begin_) == str::npos)
+        break;
+      // else skip over delimiter.
+    }
+    while (token_end_ != end_ && delims_.find(*token_end_) == str::npos)
+      ++token_end_;
+    return true;
+  }
+
+  // Implementation of GetNext() for when we have to take quotes into account.
+  bool FullGetNext() {
+    AdvanceState state;
+    token_is_delim_ = false;
+    for (;;) {
+      token_begin_ = token_end_;
+      if (token_end_ == end_)
+        return false;
+      ++token_end_;
+      if (AdvanceOne(&state, *token_begin_))
+        break;
+      if (options_ & RETURN_DELIMS) {
+        token_is_delim_ = true;
+        return true;
+      }
+      // else skip over delimiter.
+    }
+    while (token_end_ != end_ && AdvanceOne(&state, *token_end_))
+      ++token_end_;
+    return true;
+  }
+
+  bool IsDelim(char_type c) const { return delims_.find(c) != str::npos; }
+
+  bool IsQuote(char_type c) const { return quotes_.find(c) != str::npos; }
+
+  struct AdvanceState {
+    bool in_quote;
+    bool in_escape;
+    char_type quote_char;
+    AdvanceState() : in_quote(false), in_escape(false), quote_char('\0') {}
+  };
+
+  // Returns true if a delimiter was not hit.
+  bool AdvanceOne(AdvanceState* state, char_type c) {
+    if (state->in_quote) {
+      if (state->in_escape) {
+        state->in_escape = false;
+      } else if (c == '\\') {
+        state->in_escape = true;
+      } else if (c == state->quote_char) {
+        state->in_quote = false;
+      }
+    } else {
+      if (IsDelim(c))
+        return false;
+      state->in_quote = IsQuote(state->quote_char = c);
+    }
+    return true;
+  }
+
+  const_iterator start_pos_;
+  const_iterator token_begin_;
+  const_iterator token_end_;
+  const_iterator end_;
+  str delims_;
+  str quotes_;
+  int options_;
+  bool token_is_delim_;
+};
+
+typedef StringTokenizerT<std::string, std::string::const_iterator>
+    StringTokenizer;
+typedef StringTokenizerT<std::u16string, std::u16string::const_iterator>
+    WStringTokenizer;
+typedef StringTokenizerT<std::string, const char*> CStringTokenizer;
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_TOKENIZER_H_
diff --git a/src/base/strings/string_util.cc b/src/base/strings/string_util.cc
new file mode 100644 (file)
index 0000000..52049a8
--- /dev/null
@@ -0,0 +1,1047 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_util.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "util/build_config.h"
+
+namespace base {
+
+namespace {
+
+// Used by ReplaceStringPlaceholders to track the position in the string of
+// replaced parameters.
+struct ReplacementOffset {
+  ReplacementOffset(uintptr_t parameter, size_t offset)
+      : parameter(parameter), offset(offset) {}
+
+  // Index of the parameter.
+  uintptr_t parameter;
+
+  // Starting position in the string.
+  size_t offset;
+};
+
+static bool CompareParameter(const ReplacementOffset& elem1,
+                             const ReplacementOffset& elem2) {
+  return elem1.parameter < elem2.parameter;
+}
+
+// Assuming that a pointer is the size of a "machine word", then
+// uintptr_t is an integer type that is also a machine word.
+typedef uintptr_t MachineWord;
+const uintptr_t kMachineWordAlignmentMask = sizeof(MachineWord) - 1;
+
+inline bool IsAlignedToMachineWord(const void* pointer) {
+  return !(reinterpret_cast<MachineWord>(pointer) & kMachineWordAlignmentMask);
+}
+
+template <typename T>
+inline T* AlignToMachineWord(T* pointer) {
+  return reinterpret_cast<T*>(reinterpret_cast<MachineWord>(pointer) &
+                              ~kMachineWordAlignmentMask);
+}
+
+template <size_t size, typename CharacterType>
+struct NonASCIIMask;
+template <>
+struct NonASCIIMask<4, char16_t> {
+  static inline uint32_t value() { return 0xFF80FF80U; }
+};
+template <>
+struct NonASCIIMask<4, char> {
+  static inline uint32_t value() { return 0x80808080U; }
+};
+template <>
+struct NonASCIIMask<8, char16_t> {
+  static inline uint64_t value() { return 0xFF80FF80FF80FF80ULL; }
+};
+template <>
+struct NonASCIIMask<8, char> {
+  static inline uint64_t value() { return 0x8080808080808080ULL; }
+};
+
+}  // namespace
+
+namespace {
+
+template <typename StringType>
+StringType ToLowerASCIIImpl(
+    std::basic_string_view<typename StringType::value_type> str) {
+  StringType ret;
+  ret.reserve(str.size());
+  for (size_t i = 0; i < str.size(); i++)
+    ret.push_back(ToLowerASCII(str[i]));
+  return ret;
+}
+
+template <typename StringType>
+StringType ToUpperASCIIImpl(
+    std::basic_string_view<typename StringType::value_type> str) {
+  StringType ret;
+  ret.reserve(str.size());
+  for (size_t i = 0; i < str.size(); i++)
+    ret.push_back(ToUpperASCII(str[i]));
+  return ret;
+}
+
+}  // namespace
+
+std::string ToLowerASCII(std::string_view str) {
+  return ToLowerASCIIImpl<std::string>(str);
+}
+
+std::u16string ToLowerASCII(std::u16string_view str) {
+  return ToLowerASCIIImpl<std::u16string>(str);
+}
+
+std::string ToUpperASCII(std::string_view str) {
+  return ToUpperASCIIImpl<std::string>(str);
+}
+
+std::u16string ToUpperASCII(std::u16string_view str) {
+  return ToUpperASCIIImpl<std::u16string>(str);
+}
+
+template <class StringType>
+int CompareCaseInsensitiveASCIIT(
+    std::basic_string_view<typename StringType::value_type> a,
+    std::basic_string_view<typename StringType::value_type> b) {
+  // Find the first characters that aren't equal and compare them.  If the end
+  // of one of the strings is found before a nonequal character, the lengths
+  // of the strings are compared.
+  size_t i = 0;
+  while (i < a.length() && i < b.length()) {
+    typename StringType::value_type lower_a = ToLowerASCII(a[i]);
+    typename StringType::value_type lower_b = ToLowerASCII(b[i]);
+    if (lower_a < lower_b)
+      return -1;
+    if (lower_a > lower_b)
+      return 1;
+    i++;
+  }
+
+  // End of one string hit before finding a different character. Expect the
+  // common case to be "strings equal" at this point so check that first.
+  if (a.length() == b.length())
+    return 0;
+
+  if (a.length() < b.length())
+    return -1;
+  return 1;
+}
+
+int CompareCaseInsensitiveASCII(std::string_view a, std::string_view b) {
+  return CompareCaseInsensitiveASCIIT<std::string>(a, b);
+}
+
+int CompareCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b) {
+  return CompareCaseInsensitiveASCIIT<std::u16string>(a, b);
+}
+
+bool EqualsCaseInsensitiveASCII(std::string_view a, std::string_view b) {
+  if (a.length() != b.length())
+    return false;
+  return CompareCaseInsensitiveASCIIT<std::string>(a, b) == 0;
+}
+
+bool EqualsCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b) {
+  if (a.length() != b.length())
+    return false;
+  return CompareCaseInsensitiveASCIIT<std::u16string>(a, b) == 0;
+}
+
+template <class StringType>
+bool ReplaceCharsT(
+    const StringType& input,
+    std::basic_string_view<typename StringType::value_type> find_any_of_these,
+    std::basic_string_view<typename StringType::value_type> replace_with,
+    StringType* output);
+
+bool ReplaceChars(const std::u16string& input,
+                  std::u16string_view replace_chars,
+                  const std::u16string& replace_with,
+                  std::u16string* output) {
+  return ReplaceCharsT(input, replace_chars, std::u16string_view(replace_with),
+                       output);
+}
+
+bool ReplaceChars(const std::string& input,
+                  std::string_view replace_chars,
+                  const std::string& replace_with,
+                  std::string* output) {
+  return ReplaceCharsT(input, replace_chars, std::string_view(replace_with),
+                       output);
+}
+
+bool RemoveChars(const std::u16string& input,
+                 std::u16string_view remove_chars,
+                 std::u16string* output) {
+  return ReplaceCharsT(input, remove_chars, std::u16string_view(), output);
+}
+
+bool RemoveChars(const std::string& input,
+                 std::string_view remove_chars,
+                 std::string* output) {
+  return ReplaceCharsT(input, remove_chars, std::string_view(), output);
+}
+
+template <typename Str>
+TrimPositions TrimStringT(
+    const Str& input,
+    std::basic_string_view<typename Str::value_type> trim_chars,
+    TrimPositions positions,
+    Str* output) {
+  // Find the edges of leading/trailing whitespace as desired. Need to use
+  // a std::string_view version of input to be able to call find* on it with the
+  // std::string_view version of trim_chars (normally the trim_chars will be a
+  // constant so avoid making a copy).
+  std::basic_string_view<typename Str::value_type> input_piece(input);
+  const size_t last_char = input.length() - 1;
+  const size_t first_good_char = (positions & TRIM_LEADING)
+                                     ? input_piece.find_first_not_of(trim_chars)
+                                     : 0;
+  const size_t last_good_char = (positions & TRIM_TRAILING)
+                                    ? input_piece.find_last_not_of(trim_chars)
+                                    : last_char;
+
+  // When the string was all trimmed, report that we stripped off characters
+  // from whichever position the caller was interested in. For empty input, we
+  // stripped no characters, but we still need to clear |output|.
+  if (input.empty() || (first_good_char == Str::npos) ||
+      (last_good_char == Str::npos)) {
+    bool input_was_empty = input.empty();  // in case output == &input
+    output->clear();
+    return input_was_empty ? TRIM_NONE : positions;
+  }
+
+  // Trim.
+  *output = input.substr(first_good_char, last_good_char - first_good_char + 1);
+
+  // Return where we trimmed from.
+  return static_cast<TrimPositions>(
+      ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) |
+      ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING));
+}
+
+bool TrimString(const std::u16string& input,
+                std::u16string_view trim_chars,
+                std::u16string* output) {
+  return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE;
+}
+
+bool TrimString(const std::string& input,
+                std::string_view trim_chars,
+                std::string* output) {
+  return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE;
+}
+
+template <typename char_type>
+std::basic_string_view<char_type> TrimStringPieceT(
+    std::basic_string_view<char_type> input,
+    std::basic_string_view<char_type> trim_chars,
+    TrimPositions positions) {
+  size_t begin =
+      (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0;
+  if (begin == std::basic_string_view<char_type>::npos)
+    return std::basic_string_view<char_type>();  // All trimmed.
+
+  size_t end = (positions & TRIM_TRAILING)
+                   ? input.find_last_not_of(trim_chars) + 1
+                   : input.size();
+  return input.substr(begin, end - begin);
+}
+
+std::u16string_view TrimString(std::u16string_view input,
+                               std::u16string_view trim_chars,
+                               TrimPositions positions) {
+  return TrimStringPieceT(input, trim_chars, positions);
+}
+
+std::string_view TrimString(std::string_view input,
+                            std::string_view trim_chars,
+                            TrimPositions positions) {
+  return TrimStringPieceT(input, trim_chars, positions);
+}
+
+void TruncateUTF8ToByteSize(const std::string& input,
+                            const size_t byte_size,
+                            std::string* output) {
+  DCHECK(output);
+  if (byte_size > input.length()) {
+    *output = input;
+    return;
+  }
+  DCHECK_LE(byte_size,
+            static_cast<uint32_t>(std::numeric_limits<int32_t>::max()));
+  // Note: This cast is necessary because CBU8_NEXT uses int32_ts.
+  int32_t truncation_length = static_cast<int32_t>(byte_size);
+  int32_t char_index = truncation_length - 1;
+  const char* data = input.data();
+
+  // Using CBU8, we will move backwards from the truncation point
+  // to the beginning of the string looking for a valid UTF8
+  // character.  Once a full UTF8 character is found, we will
+  // truncate the string to the end of that character.
+  while (char_index >= 0) {
+    int32_t prev = char_index;
+    base_icu::UChar32 code_point = 0;
+    CBU8_NEXT(data, char_index, truncation_length, code_point);
+    if (!IsValidCharacter(code_point) || !IsValidCodepoint(code_point)) {
+      char_index = prev - 1;
+    } else {
+      break;
+    }
+  }
+
+  if (char_index >= 0)
+    *output = input.substr(0, char_index);
+  else
+    output->clear();
+}
+
+TrimPositions TrimWhitespace(const std::u16string& input,
+                             TrimPositions positions,
+                             std::u16string* output) {
+  return TrimStringT(input, std::u16string_view(kWhitespaceUTF16), positions,
+                     output);
+}
+
+std::u16string_view TrimWhitespace(std::u16string_view input,
+                                   TrimPositions positions) {
+  return TrimStringPieceT(input, std::u16string_view(kWhitespaceUTF16),
+                          positions);
+}
+
+TrimPositions TrimWhitespaceASCII(const std::string& input,
+                                  TrimPositions positions,
+                                  std::string* output) {
+  return TrimStringT(input, std::string_view(kWhitespaceASCII), positions,
+                     output);
+}
+
+std::string_view TrimWhitespaceASCII(std::string_view input,
+                                     TrimPositions positions) {
+  return TrimStringPieceT(input, std::string_view(kWhitespaceASCII), positions);
+}
+
+template <typename STR>
+STR CollapseWhitespaceT(const STR& text, bool trim_sequences_with_line_breaks) {
+  STR result;
+  result.resize(text.size());
+
+  // Set flags to pretend we're already in a trimmed whitespace sequence, so we
+  // will trim any leading whitespace.
+  bool in_whitespace = true;
+  bool already_trimmed = true;
+
+  int chars_written = 0;
+  for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) {
+    if (IsUnicodeWhitespace(*i)) {
+      if (!in_whitespace) {
+        // Reduce all whitespace sequences to a single space.
+        in_whitespace = true;
+        result[chars_written++] = L' ';
+      }
+      if (trim_sequences_with_line_breaks && !already_trimmed &&
+          ((*i == '\n') || (*i == '\r'))) {
+        // Whitespace sequences containing CR or LF are eliminated entirely.
+        already_trimmed = true;
+        --chars_written;
+      }
+    } else {
+      // Non-whitespace characters are copied straight across.
+      in_whitespace = false;
+      already_trimmed = false;
+      result[chars_written++] = *i;
+    }
+  }
+
+  if (in_whitespace && !already_trimmed) {
+    // Any trailing whitespace is eliminated.
+    --chars_written;
+  }
+
+  result.resize(chars_written);
+  return result;
+}
+
+std::u16string CollapseWhitespace(const std::u16string& text,
+                                  bool trim_sequences_with_line_breaks) {
+  return CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
+}
+
+std::string CollapseWhitespaceASCII(const std::string& text,
+                                    bool trim_sequences_with_line_breaks) {
+  return CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
+}
+
+bool ContainsOnlyChars(std::string_view input, std::string_view characters) {
+  return input.find_first_not_of(characters) == std::string_view::npos;
+}
+
+bool ContainsOnlyChars(std::u16string_view input,
+                       std::u16string_view characters) {
+  return input.find_first_not_of(characters) == std::u16string_view::npos;
+}
+
+template <class Char>
+inline bool DoIsStringASCII(const Char* characters, size_t length) {
+  MachineWord all_char_bits = 0;
+  const Char* end = characters + length;
+
+  // Prologue: align the input.
+  while (!IsAlignedToMachineWord(characters) && characters != end) {
+    all_char_bits |= *characters;
+    ++characters;
+  }
+
+  // Compare the values of CPU word size.
+  const Char* word_end = AlignToMachineWord(end);
+  const size_t loop_increment = sizeof(MachineWord) / sizeof(Char);
+  while (characters < word_end) {
+    all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
+    characters += loop_increment;
+  }
+
+  // Process the remaining bytes.
+  while (characters != end) {
+    all_char_bits |= *characters;
+    ++characters;
+  }
+
+  MachineWord non_ascii_bit_mask =
+      NonASCIIMask<sizeof(MachineWord), Char>::value();
+  return !(all_char_bits & non_ascii_bit_mask);
+}
+
+bool IsStringASCII(std::string_view str) {
+  return DoIsStringASCII(str.data(), str.length());
+}
+
+bool IsStringASCII(std::u16string_view str) {
+  return DoIsStringASCII(str.data(), str.length());
+}
+
+bool IsStringUTF8(std::string_view str) {
+  const char* src = str.data();
+  int32_t src_len = static_cast<int32_t>(str.length());
+  int32_t char_index = 0;
+
+  while (char_index < src_len) {
+    int32_t code_point;
+    CBU8_NEXT(src, char_index, src_len, code_point);
+    if (!IsValidCharacter(code_point))
+      return false;
+  }
+  return true;
+}
+
+// Implementation note: Normally this function will be called with a hardcoded
+// constant for the lowercase_ascii parameter. Constructing a std::string_view
+// from a C constant requires running strlen, so the result will be two passes
+// through the buffers, one to file the length of lowercase_ascii, and one to
+// compare each letter.
+//
+// This function could have taken a const char* to avoid this and only do one
+// pass through the string. But the strlen is faster than the case-insensitive
+// compares and lets us early-exit in the case that the strings are different
+// lengths (will often be the case for non-matches). So whether one approach or
+// the other will be faster depends on the case.
+//
+// The hardcoded strings are typically very short so it doesn't matter, and the
+// string piece gives additional flexibility for the caller (doesn't have to be
+// null terminated) so we choose the std::string_view route.
+template <typename Str>
+static inline bool DoLowerCaseEqualsASCII(
+    std::basic_string_view<typename Str::value_type> str,
+    std::string_view lowercase_ascii) {
+  if (str.size() != lowercase_ascii.size())
+    return false;
+  for (size_t i = 0; i < str.size(); i++) {
+    if (ToLowerASCII(str[i]) != lowercase_ascii[i])
+      return false;
+  }
+  return true;
+}
+
+bool LowerCaseEqualsASCII(std::string_view str,
+                          std::string_view lowercase_ascii) {
+  return DoLowerCaseEqualsASCII<std::string>(str, lowercase_ascii);
+}
+
+bool LowerCaseEqualsASCII(std::u16string_view str,
+                          std::string_view lowercase_ascii) {
+  return DoLowerCaseEqualsASCII<std::u16string>(str, lowercase_ascii);
+}
+
+bool EqualsASCII(std::u16string_view str, std::string_view ascii) {
+  if (str.length() != ascii.length())
+    return false;
+  return std::equal(ascii.begin(), ascii.end(), str.begin());
+}
+
+template <typename char_type>
+bool StartsWithT(std::basic_string_view<char_type> str,
+                 std::basic_string_view<char_type> search_for,
+                 CompareCase case_sensitivity) {
+  if (search_for.size() > str.size())
+    return false;
+
+  std::basic_string_view<char_type> source = str.substr(0, search_for.size());
+
+  switch (case_sensitivity) {
+    case CompareCase::SENSITIVE:
+      return source == search_for;
+
+    case CompareCase::INSENSITIVE_ASCII:
+      return std::equal(search_for.begin(), search_for.end(), source.begin(),
+                        CaseInsensitiveCompareASCII<char_type>());
+
+    default:
+      NOTREACHED();
+      return false;
+  }
+}
+
+bool StartsWith(std::string_view str,
+                std::string_view search_for,
+                CompareCase case_sensitivity) {
+  return StartsWithT(str, search_for, case_sensitivity);
+}
+
+bool StartsWith(std::u16string_view str,
+                std::u16string_view search_for,
+                CompareCase case_sensitivity) {
+  return StartsWithT(str, search_for, case_sensitivity);
+}
+
+template <typename Str>
+bool EndsWithT(std::basic_string_view<typename Str::value_type> str,
+               std::basic_string_view<typename Str::value_type> search_for,
+               CompareCase case_sensitivity) {
+  if (search_for.size() > str.size())
+    return false;
+
+  std::basic_string_view<typename Str::value_type> source =
+      str.substr(str.size() - search_for.size(), search_for.size());
+
+  switch (case_sensitivity) {
+    case CompareCase::SENSITIVE:
+      return source == search_for;
+
+    case CompareCase::INSENSITIVE_ASCII:
+      return std::equal(
+          source.begin(), source.end(), search_for.begin(),
+          CaseInsensitiveCompareASCII<typename Str::value_type>());
+
+    default:
+      NOTREACHED();
+      return false;
+  }
+}
+
+bool EndsWith(std::string_view str,
+              std::string_view search_for,
+              CompareCase case_sensitivity) {
+  return EndsWithT<std::string>(str, search_for, case_sensitivity);
+}
+
+bool EndsWith(std::u16string_view str,
+              std::u16string_view search_for,
+              CompareCase case_sensitivity) {
+  return EndsWithT<std::u16string>(str, search_for, case_sensitivity);
+}
+
+char HexDigitToInt(char16_t c) {
+  DCHECK(IsHexDigit(c));
+  if (c >= '0' && c <= '9')
+    return static_cast<char>(c - '0');
+  if (c >= 'A' && c <= 'F')
+    return static_cast<char>(c - 'A' + 10);
+  if (c >= 'a' && c <= 'f')
+    return static_cast<char>(c - 'a' + 10);
+  return 0;
+}
+
+bool IsUnicodeWhitespace(char16_t c) {
+  // kWhitespaceWide is a NULL-terminated string
+  for (const char16_t* cur = kWhitespaceUTF16; *cur; ++cur) {
+    if (*cur == c)
+      return true;
+  }
+  return false;
+}
+
+static const char* const kByteStringsUnlocalized[] = {" B",  " kB", " MB",
+                                                      " GB", " TB", " PB"};
+
+std::u16string FormatBytesUnlocalized(int64_t bytes) {
+  double unit_amount = static_cast<double>(bytes);
+  size_t dimension = 0;
+  const int kKilo = 1024;
+  while (unit_amount >= kKilo &&
+         dimension < std::size(kByteStringsUnlocalized) - 1) {
+    unit_amount /= kKilo;
+    dimension++;
+  }
+
+  char buf[64];
+  if (bytes != 0 && dimension > 0 && unit_amount < 100) {
+    base::snprintf(buf, std::size(buf), "%.1lf%s", unit_amount,
+                   kByteStringsUnlocalized[dimension]);
+  } else {
+    base::snprintf(buf, std::size(buf), "%.0lf%s", unit_amount,
+                   kByteStringsUnlocalized[dimension]);
+  }
+
+  return ASCIIToUTF16(buf);
+}
+
+// A Matcher for DoReplaceMatchesAfterOffset() that matches substrings.
+template <class StringType>
+struct SubstringMatcher {
+  std::basic_string_view<typename StringType::value_type> find_this;
+
+  size_t Find(const StringType& input, size_t pos) {
+    return input.find(find_this.data(), pos, find_this.length());
+  }
+  size_t MatchSize() { return find_this.length(); }
+};
+
+// A Matcher for DoReplaceMatchesAfterOffset() that matches single characters.
+template <class StringType>
+struct CharacterMatcher {
+  std::basic_string_view<typename StringType::value_type> find_any_of_these;
+
+  size_t Find(const StringType& input, size_t pos) {
+    return input.find_first_of(find_any_of_these.data(), pos,
+                               find_any_of_these.length());
+  }
+  constexpr size_t MatchSize() { return 1; }
+};
+
+enum class ReplaceType { REPLACE_ALL, REPLACE_FIRST };
+
+// Runs in O(n) time in the length of |str|, and transforms the string without
+// reallocating when possible. Returns |true| if any matches were found.
+//
+// This is parameterized on a |Matcher| traits type, so that it can be the
+// implementation for both ReplaceChars() and ReplaceSubstringsAfterOffset().
+template <class StringType, class Matcher>
+bool DoReplaceMatchesAfterOffset(
+    StringType* str,
+    size_t initial_offset,
+    Matcher matcher,
+    std::basic_string_view<typename StringType::value_type> replace_with,
+    ReplaceType replace_type) {
+  using CharTraits = typename StringType::traits_type;
+
+  const size_t find_length = matcher.MatchSize();
+  if (!find_length)
+    return false;
+
+  // If the find string doesn't appear, there's nothing to do.
+  size_t first_match = matcher.Find(*str, initial_offset);
+  if (first_match == StringType::npos)
+    return false;
+
+  // If we're only replacing one instance, there's no need to do anything
+  // complicated.
+  const size_t replace_length = replace_with.length();
+  if (replace_type == ReplaceType::REPLACE_FIRST) {
+    str->replace(first_match, find_length, replace_with.data(), replace_length);
+    return true;
+  }
+
+  // If the find and replace strings are the same length, we can simply use
+  // replace() on each instance, and finish the entire operation in O(n) time.
+  if (find_length == replace_length) {
+    auto* buffer = &((*str)[0]);
+    for (size_t offset = first_match; offset != StringType::npos;
+         offset = matcher.Find(*str, offset + replace_length)) {
+      CharTraits::copy(buffer + offset, replace_with.data(), replace_length);
+    }
+    return true;
+  }
+
+  // Since the find and replace strings aren't the same length, a loop like the
+  // one above would be O(n^2) in the worst case, as replace() will shift the
+  // entire remaining string each time. We need to be more clever to keep things
+  // O(n).
+  //
+  // When the string is being shortened, it's possible to just shift the matches
+  // down in one pass while finding, and truncate the length at the end of the
+  // search.
+  //
+  // If the string is being lengthened, more work is required. The strategy used
+  // here is to make two find() passes through the string. The first pass counts
+  // the number of matches to determine the new size. The second pass will
+  // either construct the new string into a new buffer (if the existing buffer
+  // lacked capacity), or else -- if there is room -- create a region of scratch
+  // space after |first_match| by shifting the tail of the string to a higher
+  // index, and doing in-place moves from the tail to lower indices thereafter.
+  size_t str_length = str->length();
+  size_t expansion = 0;
+  if (replace_length > find_length) {
+    // This operation lengthens the string; determine the new length by counting
+    // matches.
+    const size_t expansion_per_match = (replace_length - find_length);
+    size_t num_matches = 0;
+    for (size_t match = first_match; match != StringType::npos;
+         match = matcher.Find(*str, match + find_length)) {
+      expansion += expansion_per_match;
+      ++num_matches;
+    }
+    const size_t final_length = str_length + expansion;
+
+    if (str->capacity() < final_length) {
+      // If we'd have to allocate a new buffer to grow the string, build the
+      // result directly into the new allocation via append().
+      StringType src(str->get_allocator());
+      str->swap(src);
+      str->reserve(final_length);
+
+      size_t pos = 0;
+      for (size_t match = first_match;; match = matcher.Find(src, pos)) {
+        str->append(src, pos, match - pos);
+        str->append(replace_with.data(), replace_length);
+        pos = match + find_length;
+
+        // A mid-loop test/break enables skipping the final Find() call; the
+        // number of matches is known, so don't search past the last one.
+        if (!--num_matches)
+          break;
+      }
+
+      // Handle substring after the final match.
+      str->append(src, pos, str_length - pos);
+      return true;
+    }
+
+    // Prepare for the copy/move loop below -- expand the string to its final
+    // size by shifting the data after the first match to the end of the resized
+    // string.
+    size_t shift_src = first_match + find_length;
+    size_t shift_dst = shift_src + expansion;
+
+    // Big |expansion| factors (relative to |str_length|) require padding up to
+    // |shift_dst|.
+    if (shift_dst > str_length)
+      str->resize(shift_dst);
+
+    str->replace(shift_dst, str_length - shift_src, *str, shift_src,
+                 str_length - shift_src);
+    str_length = final_length;
+  }
+
+  // We can alternate replacement and move operations. This won't overwrite the
+  // unsearched region of the string so long as |write_offset| <= |read_offset|;
+  // that condition is always satisfied because:
+  //
+  //   (a) If the string is being shortened, |expansion| is zero and
+  //       |write_offset| grows slower than |read_offset|.
+  //
+  //   (b) If the string is being lengthened, |write_offset| grows faster than
+  //       |read_offset|, but |expansion| is big enough so that |write_offset|
+  //       will only catch up to |read_offset| at the point of the last match.
+  auto* buffer = &((*str)[0]);
+  size_t write_offset = first_match;
+  size_t read_offset = first_match + expansion;
+  do {
+    if (replace_length) {
+      CharTraits::copy(buffer + write_offset, replace_with.data(),
+                       replace_length);
+      write_offset += replace_length;
+    }
+    read_offset += find_length;
+
+    // min() clamps StringType::npos (the largest unsigned value) to str_length.
+    size_t match = std::min(matcher.Find(*str, read_offset), str_length);
+
+    size_t length = match - read_offset;
+    if (length) {
+      CharTraits::move(buffer + write_offset, buffer + read_offset, length);
+      write_offset += length;
+      read_offset += length;
+    }
+  } while (read_offset < str_length);
+
+  // If we're shortening the string, truncate it now.
+  str->resize(write_offset);
+  return true;
+}
+
+template <class StringType>
+bool ReplaceCharsT(
+    const StringType& input,
+    std::basic_string_view<typename StringType::value_type> find_any_of_these,
+    std::basic_string_view<typename StringType::value_type> replace_with,
+    StringType* output) {
+  // Commonly, this is called with output and input being the same string; in
+  // that case, this assignment is inexpensive.
+  *output = input;
+
+  return DoReplaceMatchesAfterOffset(
+      output, 0, CharacterMatcher<StringType>{find_any_of_these}, replace_with,
+      ReplaceType::REPLACE_ALL);
+}
+
+void ReplaceFirstSubstringAfterOffset(std::u16string* str,
+                                      size_t start_offset,
+                                      std::u16string_view find_this,
+                                      std::u16string_view replace_with) {
+  DoReplaceMatchesAfterOffset(str, start_offset,
+                              SubstringMatcher<std::u16string>{find_this},
+                              replace_with, ReplaceType::REPLACE_FIRST);
+}
+
+void ReplaceFirstSubstringAfterOffset(std::string* str,
+                                      size_t start_offset,
+                                      std::string_view find_this,
+                                      std::string_view replace_with) {
+  DoReplaceMatchesAfterOffset(str, start_offset,
+                              SubstringMatcher<std::string>{find_this},
+                              replace_with, ReplaceType::REPLACE_FIRST);
+}
+
+void ReplaceSubstringsAfterOffset(std::u16string* str,
+                                  size_t start_offset,
+                                  std::u16string_view find_this,
+                                  std::u16string_view replace_with) {
+  DoReplaceMatchesAfterOffset(str, start_offset,
+                              SubstringMatcher<std::u16string>{find_this},
+                              replace_with, ReplaceType::REPLACE_ALL);
+}
+
+void ReplaceSubstringsAfterOffset(std::string* str,
+                                  size_t start_offset,
+                                  std::string_view find_this,
+                                  std::string_view replace_with) {
+  DoReplaceMatchesAfterOffset(str, start_offset,
+                              SubstringMatcher<std::string>{find_this},
+                              replace_with, ReplaceType::REPLACE_ALL);
+}
+
+template <class string_type>
+inline typename string_type::value_type* WriteIntoT(string_type* str,
+                                                    size_t length_with_null) {
+  DCHECK_GT(length_with_null, 1u);
+  str->reserve(length_with_null);
+  str->resize(length_with_null - 1);
+  return &((*str)[0]);
+}
+
+char* WriteInto(std::string* str, size_t length_with_null) {
+  return WriteIntoT(str, length_with_null);
+}
+
+char16_t* WriteInto(std::u16string* str, size_t length_with_null) {
+  return WriteIntoT(str, length_with_null);
+}
+
+#if defined(_MSC_VER) && !defined(__clang__)
+// Work around VC++ code-gen bug. https://crbug.com/804884
+#pragma optimize("", off)
+#endif
+
+// Generic version for all JoinString overloads. |list_type| must be a sequence
+// (std::vector or std::initializer_list) of strings/string_views of any type.
+template <typename char_type, typename list_type>
+static std::basic_string<char_type> JoinStringT(
+    const list_type& parts,
+    std::basic_string_view<char_type> sep) {
+  if (parts.size() == 0)
+    return std::basic_string<char_type>();
+
+  // Pre-allocate the eventual size of the string. Start with the size of all of
+  // the separators (note that this *assumes* parts.size() > 0).
+  size_t total_size = (parts.size() - 1) * sep.size();
+  for (const auto& part : parts)
+    total_size += part.size();
+  std::basic_string<char_type> result;
+  result.reserve(total_size);
+
+  auto iter = parts.begin();
+  DCHECK(iter != parts.end());
+  result.append(*iter);
+  ++iter;
+
+  for (; iter != parts.end(); ++iter) {
+    result.append(sep);
+    result.append(*iter);
+  }
+
+  // Sanity-check that we pre-allocated correctly.
+  DCHECK_EQ(total_size, result.size());
+
+  return result;
+}
+
+std::string JoinString(const std::vector<std::string>& parts,
+                       std::string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+std::u16string JoinString(const std::vector<std::u16string>& parts,
+                          std::u16string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+#if defined(_MSC_VER) && !defined(__clang__)
+// Work around VC++ code-gen bug. https://crbug.com/804884
+#pragma optimize("", on)
+#endif
+
+std::string JoinString(const std::vector<std::string_view>& parts,
+                       std::string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+std::u16string JoinString(const std::vector<std::u16string_view>& parts,
+                          std::u16string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+std::string JoinString(std::initializer_list<std::string_view> parts,
+                       std::string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+std::u16string JoinString(std::initializer_list<std::u16string_view> parts,
+                          std::u16string_view separator) {
+  return JoinStringT(parts, separator);
+}
+
+template <class FormatStringType, class OutStringType>
+OutStringType DoReplaceStringPlaceholders(
+    const FormatStringType& format_string,
+    const std::vector<OutStringType>& subst,
+    std::vector<size_t>* offsets) {
+  size_t substitutions = subst.size();
+  DCHECK_LT(substitutions, 10U);
+
+  size_t sub_length = 0;
+  for (const auto& cur : subst)
+    sub_length += cur.length();
+
+  OutStringType formatted;
+  formatted.reserve(format_string.length() + sub_length);
+
+  std::vector<ReplacementOffset> r_offsets;
+  for (auto i = format_string.begin(); i != format_string.end(); ++i) {
+    if ('$' == *i) {
+      if (i + 1 != format_string.end()) {
+        ++i;
+        if ('$' == *i) {
+          while (i != format_string.end() && '$' == *i) {
+            formatted.push_back('$');
+            ++i;
+          }
+          --i;
+        } else {
+          if (*i < '1' || *i > '9') {
+            DLOG(ERROR) << "Invalid placeholder: $" << *i;
+            continue;
+          }
+          uintptr_t index = *i - '1';
+          if (offsets) {
+            ReplacementOffset r_offset(index,
+                                       static_cast<int>(formatted.size()));
+            r_offsets.insert(
+                std::upper_bound(r_offsets.begin(), r_offsets.end(), r_offset,
+                                 &CompareParameter),
+                r_offset);
+          }
+          if (index < substitutions)
+            formatted.append(subst.at(index));
+        }
+      }
+    } else {
+      formatted.push_back(*i);
+    }
+  }
+  if (offsets) {
+    for (const auto& cur : r_offsets)
+      offsets->push_back(cur.offset);
+  }
+  return formatted;
+}
+
+std::u16string ReplaceStringPlaceholders(
+    const std::u16string& format_string,
+    const std::vector<std::u16string>& subst,
+    std::vector<size_t>* offsets) {
+  return DoReplaceStringPlaceholders(format_string, subst, offsets);
+}
+
+std::string ReplaceStringPlaceholders(std::string_view format_string,
+                                      const std::vector<std::string>& subst,
+                                      std::vector<size_t>* offsets) {
+  return DoReplaceStringPlaceholders(format_string, subst, offsets);
+}
+
+std::u16string ReplaceStringPlaceholders(const std::u16string& format_string,
+                                         const std::u16string& a,
+                                         size_t* offset) {
+  std::vector<size_t> offsets;
+  std::vector<std::u16string> subst;
+  subst.push_back(a);
+  std::u16string result =
+      ReplaceStringPlaceholders(format_string, subst, &offsets);
+
+  DCHECK_EQ(1U, offsets.size());
+  if (offset)
+    *offset = offsets[0];
+  return result;
+}
+
+// The following code is compatible with the OpenBSD lcpy interface.  See:
+//   http://www.gratisoft.us/todd/papers/strlcpy.html
+//   ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c
+
+namespace {
+
+template <typename CHAR>
+size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) {
+  for (size_t i = 0; i < dst_size; ++i) {
+    if ((dst[i] = src[i]) == 0)  // We hit and copied the terminating NULL.
+      return i;
+  }
+
+  // We were left off at dst_size.  We over copied 1 byte.  Null terminate.
+  if (dst_size != 0)
+    dst[dst_size - 1] = 0;
+
+  // Count the rest of the |src|, and return it's length in characters.
+  while (src[dst_size])
+    ++dst_size;
+  return dst_size;
+}
+
+}  // namespace
+
+}  // namespace base
diff --git a/src/base/strings/string_util.h b/src/base/strings/string_util.h
new file mode 100644 (file)
index 0000000..9ed66b2
--- /dev/null
@@ -0,0 +1,424 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file defines utility functions for working with strings.
+
+#ifndef BASE_STRINGS_STRING_UTIL_H_
+#define BASE_STRINGS_STRING_UTIL_H_
+
+#include <ctype.h>
+#include <stdarg.h>  // va_list
+#include <stddef.h>
+#include <stdint.h>
+
+#include <initializer_list>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "util/build_config.h"
+
+namespace base {
+
+// C standard-library functions that aren't cross-platform are provided as
+// "base::...", and their prototypes are listed below. These functions are
+// then implemented as inline calls to the platform-specific equivalents in the
+// platform-specific headers.
+
+// Wrapper for vsnprintf that always null-terminates and always returns the
+// number of characters that would be in an untruncated formatted
+// string, even when truncation occurs.
+int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments)
+    PRINTF_FORMAT(3, 0);
+
+// Some of these implementations need to be inlined.
+
+// We separate the declaration from the implementation of this inline
+// function just so the PRINTF_FORMAT works.
+inline int snprintf(char* buffer,
+                    size_t size,
+                    _Printf_format_string_ const char* format,
+                    ...) PRINTF_FORMAT(3, 4);
+inline int snprintf(char* buffer,
+                    size_t size,
+                    _Printf_format_string_ const char* format,
+                    ...) {
+  va_list arguments;
+  va_start(arguments, format);
+  int result = vsnprintf(buffer, size, format, arguments);
+  va_end(arguments);
+  return result;
+}
+
+// ASCII-specific tolower.  The standard library's tolower is locale sensitive,
+// so we don't want to use it here.
+inline char ToLowerASCII(char c) {
+  return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
+}
+inline char16_t ToLowerASCII(char16_t c) {
+  return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
+}
+
+// ASCII-specific toupper.  The standard library's toupper is locale sensitive,
+// so we don't want to use it here.
+inline char ToUpperASCII(char c) {
+  return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c;
+}
+inline char16_t ToUpperASCII(char16_t c) {
+  return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c;
+}
+
+// Converts the given string to it's ASCII-lowercase equivalent.
+std::string ToLowerASCII(std::string_view str);
+std::u16string ToLowerASCII(std::u16string_view str);
+
+// Converts the given string to it's ASCII-uppercase equivalent.
+std::string ToUpperASCII(std::string_view str);
+std::u16string ToUpperASCII(std::u16string_view str);
+
+// Functor for case-insensitive ASCII comparisons for STL algorithms like
+// std::search.
+//
+// Note that a full Unicode version of this functor is not possible to write
+// because case mappings might change the number of characters, depend on
+// context (combining accents), and require handling UTF-16. If you need
+// proper Unicode support, use base::i18n::ToLower/FoldCase and then just
+// use a normal operator== on the result.
+template <typename Char>
+struct CaseInsensitiveCompareASCII {
+ public:
+  bool operator()(Char x, Char y) const {
+    return ToLowerASCII(x) == ToLowerASCII(y);
+  }
+};
+
+// Like strcasecmp for case-insensitive ASCII characters only. Returns:
+//   -1  (a < b)
+//    0  (a == b)
+//    1  (a > b)
+// (unlike strcasecmp which can return values greater or less than 1/-1). For
+// full Unicode support, use base::i18n::ToLower or base::i18h::FoldCase
+// and then just call the normal string operators on the result.
+int CompareCaseInsensitiveASCII(std::string_view a, std::string_view b);
+int CompareCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b);
+
+// Equality for ASCII case-insensitive comparisons. For full Unicode support,
+// use base::i18n::ToLower or base::i18h::FoldCase and then compare with either
+// == or !=.
+bool EqualsCaseInsensitiveASCII(std::string_view a, std::string_view b);
+bool EqualsCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b);
+
+// Contains the set of characters representing whitespace in the corresponding
+// encoding. Null-terminated. The ASCII versions are the whitespaces as defined
+// by HTML5, and don't include control characters.
+extern const char16_t kWhitespaceUTF16[];  // Includes Unicode.
+extern const char kWhitespaceASCII[];
+extern const char16_t kWhitespaceASCIIAs16[];  // No unicode.
+
+// Null-terminated string representing the UTF-8 byte order mark.
+extern const char kUtf8ByteOrderMark[];
+
+// Removes characters in |remove_chars| from anywhere in |input|.  Returns true
+// if any characters were removed.  |remove_chars| must be null-terminated.
+// NOTE: Safe to use the same variable for both |input| and |output|.
+bool RemoveChars(const std::u16string& input,
+                 std::u16string_view remove_chars,
+                 std::u16string* output);
+bool RemoveChars(const std::string& input,
+                 std::string_view remove_chars,
+                 std::string* output);
+
+// Replaces characters in |replace_chars| from anywhere in |input| with
+// |replace_with|.  Each character in |replace_chars| will be replaced with
+// the |replace_with| string.  Returns true if any characters were replaced.
+// |replace_chars| must be null-terminated.
+// NOTE: Safe to use the same variable for both |input| and |output|.
+bool ReplaceChars(const std::u16string& input,
+                  std::u16string_view replace_chars,
+                  const std::u16string& replace_with,
+                  std::u16string* output);
+bool ReplaceChars(const std::string& input,
+                  std::string_view replace_chars,
+                  const std::string& replace_with,
+                  std::string* output);
+
+enum TrimPositions {
+  TRIM_NONE = 0,
+  TRIM_LEADING = 1 << 0,
+  TRIM_TRAILING = 1 << 1,
+  TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,
+};
+
+// Removes characters in |trim_chars| from the beginning and end of |input|.
+// The 8-bit version only works on 8-bit characters, not UTF-8. Returns true if
+// any characters were removed.
+//
+// It is safe to use the same variable for both |input| and |output| (this is
+// the normal usage to trim in-place).
+bool TrimString(const std::u16string& input,
+                std::u16string_view trim_chars,
+                std::u16string* output);
+bool TrimString(const std::string& input,
+                std::string_view trim_chars,
+                std::string* output);
+
+// std::string_view versions of the above. The returned pieces refer to the
+// original buffer.
+std::u16string_view TrimString(std::u16string_view input,
+                               std::u16string_view trim_chars,
+                               TrimPositions positions);
+std::string_view TrimString(std::string_view input,
+                            std::string_view trim_chars,
+                            TrimPositions positions);
+
+// Truncates a string to the nearest UTF-8 character that will leave
+// the string less than or equal to the specified byte size.
+void TruncateUTF8ToByteSize(const std::string& input,
+                            const size_t byte_size,
+                            std::string* output);
+
+// Trims any whitespace from either end of the input string.
+//
+// The std::string_view versions return a substring referencing the input
+// buffer. The ASCII versions look only for ASCII whitespace.
+//
+// The std::string versions return where whitespace was found.
+// NOTE: Safe to use the same variable for both input and output.
+TrimPositions TrimWhitespace(const std::u16string& input,
+                             TrimPositions positions,
+                             std::u16string* output);
+std::u16string_view TrimWhitespace(std::u16string_view input,
+                                   TrimPositions positions);
+TrimPositions TrimWhitespaceASCII(const std::string& input,
+                                  TrimPositions positions,
+                                  std::string* output);
+std::string_view TrimWhitespaceASCII(std::string_view input,
+                                     TrimPositions positions);
+
+// Searches for CR or LF characters.  Removes all contiguous whitespace
+// strings that contain them.  This is useful when trying to deal with text
+// copied from terminals.
+// Returns |text|, with the following three transformations:
+// (1) Leading and trailing whitespace is trimmed.
+// (2) If |trim_sequences_with_line_breaks| is true, any other whitespace
+//     sequences containing a CR or LF are trimmed.
+// (3) All other whitespace sequences are converted to single spaces.
+std::u16string CollapseWhitespace(const std::u16string& text,
+                                  bool trim_sequences_with_line_breaks);
+std::string CollapseWhitespaceASCII(const std::string& text,
+                                    bool trim_sequences_with_line_breaks);
+
+// Returns true if |input| is empty or contains only characters found in
+// |characters|.
+bool ContainsOnlyChars(std::string_view input, std::string_view characters);
+bool ContainsOnlyChars(std::u16string_view input,
+                       std::u16string_view characters);
+
+// Returns true if the specified string matches the criteria. How can a wide
+// string be 8-bit or UTF8? It contains only characters that are < 256 (in the
+// first case) or characters that use only 8-bits and whose 8-bit
+// representation looks like a UTF-8 string (the second case).
+//
+// Note that IsStringUTF8 checks not only if the input is structurally
+// valid but also if it doesn't contain any non-character codepoint
+// (e.g. U+FFFE). It's done on purpose because all the existing callers want
+// to have the maximum 'discriminating' power from other encodings. If
+// there's a use case for just checking the structural validity, we have to
+// add a new function for that.
+//
+// IsStringASCII assumes the input is likely all ASCII, and does not leave early
+// if it is not the case.
+bool IsStringUTF8(std::string_view str);
+bool IsStringASCII(std::string_view str);
+bool IsStringASCII(std::u16string_view str);
+
+// Compare the lower-case form of the given string against the given
+// previously-lower-cased ASCII string (typically a constant).
+bool LowerCaseEqualsASCII(std::string_view str,
+                          std::string_view lowecase_ascii);
+bool LowerCaseEqualsASCII(std::u16string_view str,
+                          std::string_view lowecase_ascii);
+
+// Performs a case-sensitive string compare of the given 16-bit string against
+// the given 8-bit ASCII string (typically a constant). The behavior is
+// undefined if the |ascii| string is not ASCII.
+bool EqualsASCII(std::u16string_view str, std::string_view ascii);
+
+// Indicates case sensitivity of comparisons. Only ASCII case insensitivity
+// is supported. Full Unicode case-insensitive conversions would need to go in
+// base/i18n so it can use ICU.
+//
+// If you need to do Unicode-aware case-insensitive StartsWith/EndsWith, it's
+// best to call base::i18n::ToLower() or base::i18n::FoldCase() (see
+// base/i18n/case_conversion.h for usage advice) on the arguments, and then use
+// the results to a case-sensitive comparison.
+enum class CompareCase {
+  SENSITIVE,
+  INSENSITIVE_ASCII,
+};
+
+bool StartsWith(std::string_view str,
+                std::string_view search_for,
+                CompareCase case_sensitivity);
+bool StartsWith(std::u16string_view str,
+                std::u16string_view search_for,
+                CompareCase case_sensitivity);
+bool EndsWith(std::string_view str,
+              std::string_view search_for,
+              CompareCase case_sensitivity);
+bool EndsWith(std::u16string_view str,
+              std::u16string_view search_for,
+              CompareCase case_sensitivity);
+
+// Determines the type of ASCII character, independent of locale (the C
+// library versions will change based on locale).
+template <typename Char>
+inline bool IsAsciiWhitespace(Char c) {
+  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+}
+template <typename Char>
+inline bool IsAsciiAlpha(Char c) {
+  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+template <typename Char>
+inline bool IsAsciiUpper(Char c) {
+  return c >= 'A' && c <= 'Z';
+}
+template <typename Char>
+inline bool IsAsciiLower(Char c) {
+  return c >= 'a' && c <= 'z';
+}
+template <typename Char>
+inline bool IsAsciiDigit(Char c) {
+  return c >= '0' && c <= '9';
+}
+
+template <typename Char>
+inline bool IsHexDigit(Char c) {
+  return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') ||
+         (c >= 'a' && c <= 'f');
+}
+
+// Returns the integer corresponding to the given hex character. For example:
+//    '4' -> 4
+//    'a' -> 10
+//    'B' -> 11
+// Assumes the input is a valid hex character. DCHECKs in debug builds if not.
+char HexDigitToInt(char16_t c);
+
+// Returns true if it's a Unicode whitespace character.
+bool IsUnicodeWhitespace(char16_t c);
+
+// Return a byte string in human-readable format with a unit suffix. Not
+// appropriate for use in any UI; use of FormatBytes and friends in ui/base is
+// highly recommended instead. TODO(avi): Figure out how to get callers to use
+// FormatBytes instead; remove this.
+std::u16string FormatBytesUnlocalized(int64_t bytes);
+
+// Starting at |start_offset| (usually 0), replace the first instance of
+// |find_this| with |replace_with|.
+void ReplaceFirstSubstringAfterOffset(std::u16string* str,
+                                      size_t start_offset,
+                                      std::u16string_view find_this,
+                                      std::u16string_view replace_with);
+void ReplaceFirstSubstringAfterOffset(std::string* str,
+                                      size_t start_offset,
+                                      std::string_view find_this,
+                                      std::string_view replace_with);
+
+// Starting at |start_offset| (usually 0), look through |str| and replace all
+// instances of |find_this| with |replace_with|.
+//
+// This does entire substrings; use std::replace in <algorithm> for single
+// characters, for example:
+//   std::replace(str.begin(), str.end(), 'a', 'b');
+void ReplaceSubstringsAfterOffset(std::u16string* str,
+                                  size_t start_offset,
+                                  std::u16string_view find_this,
+                                  std::u16string_view replace_with);
+void ReplaceSubstringsAfterOffset(std::string* str,
+                                  size_t start_offset,
+                                  std::string_view find_this,
+                                  std::string_view replace_with);
+
+// Reserves enough memory in |str| to accommodate |length_with_null| characters,
+// sets the size of |str| to |length_with_null - 1| characters, and returns a
+// pointer to the underlying contiguous array of characters.  This is typically
+// used when calling a function that writes results into a character array, but
+// the caller wants the data to be managed by a string-like object.  It is
+// convenient in that is can be used inline in the call, and fast in that it
+// avoids copying the results of the call from a char* into a string.
+//
+// |length_with_null| must be at least 2, since otherwise the underlying string
+// would have size 0, and trying to access &((*str)[0]) in that case can result
+// in a number of problems.
+//
+// Internally, this takes linear time because the resize() call 0-fills the
+// underlying array for potentially all
+// (|length_with_null - 1| * sizeof(string_type::value_type)) bytes.  Ideally we
+// could avoid this aspect of the resize() call, as we expect the caller to
+// immediately write over this memory, but there is no other way to set the size
+// of the string, and not doing that will mean people who access |str| rather
+// than str.c_str() will get back a string of whatever size |str| had on entry
+// to this function (probably 0).
+char* WriteInto(std::string* str, size_t length_with_null);
+char16_t* WriteInto(std::u16string* str, size_t length_with_null);
+
+// Does the opposite of SplitString()/SplitStringPiece(). Joins a vector or list
+// of strings into a single string, inserting |separator| (which may be empty)
+// in between all elements.
+//
+// If possible, callers should build a vector of std::string_views and use the
+// std::string_view variant, so that they do not create unnecessary copies of
+// strings. For example, instead of using SplitString, modifying the vector,
+// then using JoinString, use SplitStringPiece followed by JoinString so that no
+// copies of those strings are created until the final join operation.
+//
+// Use StrCat (in base/strings/strcat.h) if you don't need a separator.
+std::string JoinString(const std::vector<std::string>& parts,
+                       std::string_view separator);
+std::u16string JoinString(const std::vector<std::u16string>& parts,
+                          std::u16string_view separator);
+std::string JoinString(const std::vector<std::string_view>& parts,
+                       std::string_view separator);
+std::u16string JoinString(const std::vector<std::u16string_view>& parts,
+                          std::u16string_view separator);
+// Explicit initializer_list overloads are required to break ambiguity when used
+// with a literal initializer list (otherwise the compiler would not be able to
+// decide between the string and std::string_view overloads).
+std::string JoinString(std::initializer_list<std::string_view> parts,
+                       std::string_view separator);
+std::u16string JoinString(std::initializer_list<std::u16string_view> parts,
+                          std::u16string_view separator);
+
+// Replace $1-$2-$3..$9 in the format string with values from |subst|.
+// Additionally, any number of consecutive '$' characters is replaced by that
+// number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be
+// NULL. This only allows you to use up to nine replacements.
+std::u16string ReplaceStringPlaceholders(
+    const std::u16string& format_string,
+    const std::vector<std::u16string>& subst,
+    std::vector<size_t>* offsets);
+
+std::string ReplaceStringPlaceholders(std::string_view format_string,
+                                      const std::vector<std::string>& subst,
+                                      std::vector<size_t>* offsets);
+
+// Single-string shortcut for ReplaceStringHolders. |offset| may be NULL.
+std::u16string ReplaceStringPlaceholders(const std::u16string& format_string,
+                                         const std::u16string& a,
+                                         size_t* offset);
+
+}  // namespace base
+
+#if defined(OS_WIN)
+#include "base/strings/string_util_win.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/strings/string_util_posix.h"
+#else
+#error Define string operations appropriately for your platform
+#endif
+
+#endif  // BASE_STRINGS_STRING_UTIL_H_
diff --git a/src/base/strings/string_util_constants.cc b/src/base/strings/string_util_constants.cc
new file mode 100644 (file)
index 0000000..de88454
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_util.h"
+
+namespace base {
+
+#define WHITESPACE_UNICODE                    \
+  0x0009,     /* CHARACTER TABULATION */      \
+      0x000A, /* LINE FEED (LF) */            \
+      0x000B, /* LINE TABULATION */           \
+      0x000C, /* FORM FEED (FF) */            \
+      0x000D, /* CARRIAGE RETURN (CR) */      \
+      0x0020, /* SPACE */                     \
+      0x0085, /* NEXT LINE (NEL) */           \
+      0x00A0, /* NO-BREAK SPACE */            \
+      0x1680, /* OGHAM SPACE MARK */          \
+      0x2000, /* EN QUAD */                   \
+      0x2001, /* EM QUAD */                   \
+      0x2002, /* EN SPACE */                  \
+      0x2003, /* EM SPACE */                  \
+      0x2004, /* THREE-PER-EM SPACE */        \
+      0x2005, /* FOUR-PER-EM SPACE */         \
+      0x2006, /* SIX-PER-EM SPACE */          \
+      0x2007, /* FIGURE SPACE */              \
+      0x2008, /* PUNCTUATION SPACE */         \
+      0x2009, /* THIN SPACE */                \
+      0x200A, /* HAIR SPACE */                \
+      0x2028, /* LINE SEPARATOR */            \
+      0x2029, /* PARAGRAPH SEPARATOR */       \
+      0x202F, /* NARROW NO-BREAK SPACE */     \
+      0x205F, /* MEDIUM MATHEMATICAL SPACE */ \
+      0x3000, /* IDEOGRAPHIC SPACE */         \
+      0
+
+const char16_t kWhitespaceUTF16[] = {WHITESPACE_UNICODE};
+
+const char kWhitespaceASCII[] = {0x09,  // CHARACTER TABULATION
+                                 0x0A,  // LINE FEED (LF)
+                                 0x0B,  // LINE TABULATION
+                                 0x0C,  // FORM FEED (FF)
+                                 0x0D,  // CARRIAGE RETURN (CR)
+                                 0x20,  // SPACE
+                                 0};
+
+const char16_t kWhitespaceASCIIAs16[] = {0x09,  // CHARACTER TABULATION
+                                         0x0A,  // LINE FEED (LF)
+                                         0x0B,  // LINE TABULATION
+                                         0x0C,  // FORM FEED (FF)
+                                         0x0D,  // CARRIAGE RETURN (CR)
+                                         0x20,  // SPACE
+                                         0};
+
+const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF";
+
+}  // namespace base
diff --git a/src/base/strings/string_util_posix.h b/src/base/strings/string_util_posix.h
new file mode 100644 (file)
index 0000000..83be1db
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_UTIL_POSIX_H_
+#define BASE_STRINGS_STRING_UTIL_POSIX_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "base/logging.h"
+
+namespace base {
+
+inline int vsnprintf(char* buffer,
+                     size_t size,
+                     const char* format,
+                     va_list arguments) {
+  return ::vsnprintf(buffer, size, format, arguments);
+}
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_UTIL_POSIX_H_
diff --git a/src/base/strings/string_util_win.h b/src/base/strings/string_util_win.h
new file mode 100644 (file)
index 0000000..717a72c
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_UTIL_WIN_H_
+#define BASE_STRINGS_STRING_UTIL_WIN_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "base/logging.h"
+
+namespace base {
+
+inline int vsnprintf(char* buffer,
+                     size_t size,
+                     const char* format,
+                     va_list arguments) {
+  int length = vsnprintf_s(buffer, size, size - 1, format, arguments);
+  if (length < 0)
+    return _vscprintf(format, arguments);
+  return length;
+}
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_UTIL_WIN_H_
diff --git a/src/base/strings/stringize_macros.h b/src/base/strings/stringize_macros.h
new file mode 100644 (file)
index 0000000..251c443
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file defines preprocessor macros for stringizing preprocessor
+// symbols (or their output) and manipulating preprocessor symbols
+// that define strings.
+
+#ifndef BASE_STRINGS_STRINGIZE_MACROS_H_
+#define BASE_STRINGS_STRINGIZE_MACROS_H_
+
+#include "util/build_config.h"
+
+// This is not very useful as it does not expand defined symbols if
+// called directly. Use its counterpart without the _NO_EXPANSION
+// suffix, below.
+#define STRINGIZE_NO_EXPANSION(x) #x
+
+// Use this to quote the provided parameter, first expanding it if it
+// is a preprocessor symbol.
+//
+// For example, if:
+//   #define A FOO
+//   #define B(x) myobj->FunctionCall(x)
+//
+// Then:
+//   STRINGIZE(A) produces "FOO"
+//   STRINGIZE(B(y)) produces "myobj->FunctionCall(y)"
+#define STRINGIZE(x) STRINGIZE_NO_EXPANSION(x)
+
+#endif  // BASE_STRINGS_STRINGIZE_MACROS_H_
diff --git a/src/base/strings/stringprintf.cc b/src/base/strings/stringprintf.cc
new file mode 100644 (file)
index 0000000..c00bdbc
--- /dev/null
@@ -0,0 +1,143 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/stringprintf.h"
+
+#include <errno.h>
+#include <stddef.h>
+
+#include <iterator>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/scoped_clear_errno.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/build_config.h"
+
+namespace base {
+
+namespace {
+
+// Overloaded wrappers around vsnprintf and vswprintf. The buf_size parameter
+// is the size of the buffer. These return the number of characters in the
+// formatted string excluding the NUL terminator. If the buffer is not
+// large enough to accommodate the formatted string without truncation, they
+// return the number of characters that would be in the fully-formatted string
+// (vsnprintf, and vswprintf on Windows), or -1 (vswprintf on POSIX platforms).
+inline int vsnprintfT(char* buffer,
+                      size_t buf_size,
+                      const char* format,
+                      va_list argptr) {
+  return base::vsnprintf(buffer, buf_size, format, argptr);
+}
+
+// Templatized backend for StringPrintF/StringAppendF. This does not finalize
+// the va_list, the caller is expected to do that.
+template <class StringType>
+static void StringAppendVT(StringType* dst,
+                           const typename StringType::value_type* format,
+                           va_list ap) {
+  // First try with a small fixed size buffer.
+  // This buffer size should be kept in sync with StringUtilTest.GrowBoundary
+  // and StringUtilTest.StringPrintfBounds.
+  typename StringType::value_type stack_buf[1024];
+
+  va_list ap_copy;
+  va_copy(ap_copy, ap);
+
+#if !defined(OS_WIN)
+  ScopedClearErrno clear_errno;
+#endif
+  int result = vsnprintfT(stack_buf, std::size(stack_buf), format, ap_copy);
+  va_end(ap_copy);
+
+  if (result >= 0 && result < static_cast<int>(std::size(stack_buf))) {
+    // It fit.
+    dst->append(stack_buf, result);
+    return;
+  }
+
+  // Repeatedly increase buffer size until it fits.
+  int mem_length = std::size(stack_buf);
+  while (true) {
+    if (result < 0) {
+#if defined(OS_WIN)
+      // On Windows, vsnprintfT always returns the number of characters in a
+      // fully-formatted string, so if we reach this point, something else is
+      // wrong and no amount of buffer-doubling is going to fix it.
+      return;
+#else
+      if (errno != 0 && errno != EOVERFLOW)
+        return;
+      // Try doubling the buffer size.
+      mem_length *= 2;
+#endif
+    } else {
+      // We need exactly "result + 1" characters.
+      mem_length = result + 1;
+    }
+
+    if (mem_length > 32 * 1024 * 1024) {
+      // That should be plenty, don't try anything larger.  This protects
+      // against huge allocations when using vsnprintfT implementations that
+      // return -1 for reasons other than overflow without setting errno.
+      DLOG(WARNING) << "Unable to printf the requested string due to size.";
+      return;
+    }
+
+    std::vector<typename StringType::value_type> mem_buf(mem_length);
+
+    // NOTE: You can only use a va_list once.  Since we're in a while loop, we
+    // need to make a new copy each time so we don't use up the original.
+    va_copy(ap_copy, ap);
+    result = vsnprintfT(&mem_buf[0], mem_length, format, ap_copy);
+    va_end(ap_copy);
+
+    if ((result >= 0) && (result < mem_length)) {
+      // It fit.
+      dst->append(&mem_buf[0], result);
+      return;
+    }
+  }
+}
+
+}  // namespace
+
+std::string StringPrintf(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  std::string result;
+  StringAppendV(&result, format, ap);
+  va_end(ap);
+  return result;
+}
+
+std::string StringPrintV(const char* format, va_list ap) {
+  std::string result;
+  StringAppendV(&result, format, ap);
+  return result;
+}
+
+const std::string& SStringPrintf(std::string* dst, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  dst->clear();
+  StringAppendV(dst, format, ap);
+  va_end(ap);
+  return *dst;
+}
+
+void StringAppendF(std::string* dst, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  StringAppendV(dst, format, ap);
+  va_end(ap);
+}
+
+void StringAppendV(std::string* dst, const char* format, va_list ap) {
+  StringAppendVT(dst, format, ap);
+}
+
+}  // namespace base
diff --git a/src/base/strings/stringprintf.h b/src/base/strings/stringprintf.h
new file mode 100644 (file)
index 0000000..e6e0746
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRINGPRINTF_H_
+#define BASE_STRINGS_STRINGPRINTF_H_
+
+#include <stdarg.h>  // va_list
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "util/build_config.h"
+
+namespace base {
+
+// Return a C++ string given printf-like input.
+std::string StringPrintf(_Printf_format_string_ const char* format, ...)
+    PRINTF_FORMAT(1, 2) WARN_UNUSED_RESULT;
+
+// Return a C++ string given vprintf-like input.
+std::string StringPrintV(const char* format, va_list ap)
+    PRINTF_FORMAT(1, 0) WARN_UNUSED_RESULT;
+
+// Store result into a supplied string and return it.
+const std::string& SStringPrintf(std::string* dst,
+                                 _Printf_format_string_ const char* format,
+                                 ...) PRINTF_FORMAT(2, 3);
+
+// Append result to a supplied string.
+void StringAppendF(std::string* dst,
+                   _Printf_format_string_ const char* format,
+                   ...) PRINTF_FORMAT(2, 3);
+
+// Lower-level routine that takes a va_list and appends to a specified
+// string.  All other routines are just convenience wrappers around it.
+void StringAppendV(std::string* dst, const char* format, va_list ap)
+    PRINTF_FORMAT(2, 0);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRINGPRINTF_H_
diff --git a/src/base/strings/utf_offset_string_conversions.cc b/src/base/strings/utf_offset_string_conversions.cc
new file mode 100644 (file)
index 0000000..ee55a24
--- /dev/null
@@ -0,0 +1,266 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/utf_offset_string_conversions.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversion_utils.h"
+
+namespace base {
+
+OffsetAdjuster::Adjustment::Adjustment(size_t original_offset,
+                                       size_t original_length,
+                                       size_t output_length)
+    : original_offset(original_offset),
+      original_length(original_length),
+      output_length(output_length) {}
+
+// static
+void OffsetAdjuster::AdjustOffsets(const Adjustments& adjustments,
+                                   std::vector<size_t>* offsets_for_adjustment,
+                                   size_t limit) {
+  DCHECK(offsets_for_adjustment);
+  for (std::vector<size_t>::iterator i(offsets_for_adjustment->begin());
+       i != offsets_for_adjustment->end(); ++i)
+    AdjustOffset(adjustments, &(*i), limit);
+}
+
+// static
+void OffsetAdjuster::AdjustOffset(const Adjustments& adjustments,
+                                  size_t* offset,
+                                  size_t limit) {
+  DCHECK(offset);
+  if (*offset == std::u16string::npos)
+    return;
+  int adjustment = 0;
+  for (Adjustments::const_iterator i = adjustments.begin();
+       i != adjustments.end(); ++i) {
+    if (*offset <= i->original_offset)
+      break;
+    if (*offset < (i->original_offset + i->original_length)) {
+      *offset = std::u16string::npos;
+      return;
+    }
+    adjustment += static_cast<int>(i->original_length - i->output_length);
+  }
+  *offset -= adjustment;
+
+  if (*offset > limit)
+    *offset = std::u16string::npos;
+}
+
+// static
+void OffsetAdjuster::UnadjustOffsets(
+    const Adjustments& adjustments,
+    std::vector<size_t>* offsets_for_unadjustment) {
+  if (!offsets_for_unadjustment || adjustments.empty())
+    return;
+  for (std::vector<size_t>::iterator i(offsets_for_unadjustment->begin());
+       i != offsets_for_unadjustment->end(); ++i)
+    UnadjustOffset(adjustments, &(*i));
+}
+
+// static
+void OffsetAdjuster::UnadjustOffset(const Adjustments& adjustments,
+                                    size_t* offset) {
+  if (*offset == std::u16string::npos)
+    return;
+  int adjustment = 0;
+  for (Adjustments::const_iterator i = adjustments.begin();
+       i != adjustments.end(); ++i) {
+    if (*offset + adjustment <= i->original_offset)
+      break;
+    adjustment += static_cast<int>(i->original_length - i->output_length);
+    if ((*offset + adjustment) < (i->original_offset + i->original_length)) {
+      *offset = std::u16string::npos;
+      return;
+    }
+  }
+  *offset += adjustment;
+}
+
+// static
+void OffsetAdjuster::MergeSequentialAdjustments(
+    const Adjustments& first_adjustments,
+    Adjustments* adjustments_on_adjusted_string) {
+  Adjustments::iterator adjusted_iter = adjustments_on_adjusted_string->begin();
+  Adjustments::const_iterator first_iter = first_adjustments.begin();
+  // Simultaneously iterate over all |adjustments_on_adjusted_string| and
+  // |first_adjustments|, adding adjustments to or correcting the adjustments
+  // in |adjustments_on_adjusted_string| as we go.  |shift| keeps track of the
+  // current number of characters collapsed by |first_adjustments| up to this
+  // point.  |currently_collapsing| keeps track of the number of characters
+  // collapsed by |first_adjustments| into the current |adjusted_iter|'s
+  // length.  These are characters that will change |shift| as soon as we're
+  // done processing the current |adjusted_iter|; they are not yet reflected in
+  // |shift|.
+  size_t shift = 0;
+  size_t currently_collapsing = 0;
+  while (adjusted_iter != adjustments_on_adjusted_string->end()) {
+    if ((first_iter == first_adjustments.end()) ||
+        ((adjusted_iter->original_offset + shift +
+          adjusted_iter->original_length) <= first_iter->original_offset)) {
+      // Entire |adjusted_iter| (accounting for its shift and including its
+      // whole original length) comes before |first_iter|.
+      //
+      // Correct the offset at |adjusted_iter| and move onto the next
+      // adjustment that needs revising.
+      adjusted_iter->original_offset += shift;
+      shift += currently_collapsing;
+      currently_collapsing = 0;
+      ++adjusted_iter;
+    } else if ((adjusted_iter->original_offset + shift) >
+               first_iter->original_offset) {
+      // |first_iter| comes before the |adjusted_iter| (as adjusted by |shift|).
+
+      // It's not possible for the adjustments to overlap.  (It shouldn't
+      // be possible that we have an |adjusted_iter->original_offset| that,
+      // when adjusted by the computed |shift|, is in the middle of
+      // |first_iter|'s output's length.  After all, that would mean the
+      // current adjustment_on_adjusted_string somehow points to an offset
+      // that was supposed to have been eliminated by the first set of
+      // adjustments.)
+      DCHECK_LE(first_iter->original_offset + first_iter->output_length,
+                adjusted_iter->original_offset + shift);
+
+      // Add the |first_adjustment_iter| to the full set of adjustments while
+      // making sure |adjusted_iter| continues pointing to the same element.
+      // We do this by inserting the |first_adjustment_iter| right before
+      // |adjusted_iter|, then incrementing |adjusted_iter| so it points to
+      // the following element.
+      shift += first_iter->original_length - first_iter->output_length;
+      adjusted_iter =
+          adjustments_on_adjusted_string->insert(adjusted_iter, *first_iter);
+      ++adjusted_iter;
+      ++first_iter;
+    } else {
+      // The first adjustment adjusted something that then got further adjusted
+      // by the second set of adjustments.  In other words, |first_iter| points
+      // to something in the range covered by |adjusted_iter|'s length (after
+      // accounting for |shift|).  Precisely,
+      //   adjusted_iter->original_offset + shift
+      //   <=
+      //   first_iter->original_offset
+      //   <=
+      //   adjusted_iter->original_offset + shift +
+      //       adjusted_iter->original_length
+
+      // Modify the current |adjusted_iter| to include whatever collapsing
+      // happened in |first_iter|, then advance to the next |first_adjustments|
+      // because we dealt with the current one.
+      const int collapse = static_cast<int>(first_iter->original_length) -
+                           static_cast<int>(first_iter->output_length);
+      // This function does not know how to deal with a string that expands and
+      // then gets modified, only strings that collapse and then get modified.
+      DCHECK_GT(collapse, 0);
+      adjusted_iter->original_length += collapse;
+      currently_collapsing += collapse;
+      ++first_iter;
+    }
+  }
+  DCHECK_EQ(0u, currently_collapsing);
+  if (first_iter != first_adjustments.end()) {
+    // Only first adjustments are left.  These do not need to be modified.
+    // (Their offsets are already correct with respect to the original string.)
+    // Append them all.
+    DCHECK(adjusted_iter == adjustments_on_adjusted_string->end());
+    adjustments_on_adjusted_string->insert(
+        adjustments_on_adjusted_string->end(), first_iter,
+        first_adjustments.end());
+  }
+}
+
+// Converts the given source Unicode character type to the given destination
+// Unicode character type as a STL string. The given input buffer and size
+// determine the source, and the given output STL string will be replaced by
+// the result.  If non-NULL, |adjustments| is set to reflect the all the
+// alterations to the string that are not one-character-to-one-character.
+// It will always be sorted by increasing offset.
+template <typename SrcChar, typename DestStdString>
+bool ConvertUnicode(const SrcChar* src,
+                    size_t src_len,
+                    DestStdString* output,
+                    OffsetAdjuster::Adjustments* adjustments) {
+  if (adjustments)
+    adjustments->clear();
+  // ICU requires 32-bit numbers.
+  bool success = true;
+  int32_t src_len32 = static_cast<int32_t>(src_len);
+  for (int32_t i = 0; i < src_len32; i++) {
+    uint32_t code_point;
+    size_t original_i = i;
+    size_t chars_written = 0;
+    if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) {
+      chars_written = WriteUnicodeCharacter(code_point, output);
+    } else {
+      chars_written = WriteUnicodeCharacter(0xFFFD, output);
+      success = false;
+    }
+
+    // Only bother writing an adjustment if this modification changed the
+    // length of this character.
+    // NOTE: ReadUnicodeCharacter() adjusts |i| to point _at_ the last
+    // character read, not after it (so that incrementing it in the loop
+    // increment will place it at the right location), so we need to account
+    // for that in determining the amount that was read.
+    if (adjustments && ((i - original_i + 1) != chars_written)) {
+      adjustments->push_back(OffsetAdjuster::Adjustment(
+          original_i, i - original_i + 1, chars_written));
+    }
+  }
+  return success;
+}
+
+bool UTF8ToUTF16WithAdjustments(
+    const char* src,
+    size_t src_len,
+    std::u16string* output,
+    base::OffsetAdjuster::Adjustments* adjustments) {
+  PrepareForUTF16Or32Output(src, src_len, output);
+  return ConvertUnicode(src, src_len, output, adjustments);
+}
+
+std::u16string UTF8ToUTF16WithAdjustments(
+    const std::string_view& utf8,
+    base::OffsetAdjuster::Adjustments* adjustments) {
+  std::u16string result;
+  UTF8ToUTF16WithAdjustments(utf8.data(), utf8.length(), &result, adjustments);
+  return result;
+}
+
+std::u16string UTF8ToUTF16AndAdjustOffsets(
+    const std::string_view& utf8,
+    std::vector<size_t>* offsets_for_adjustment) {
+  for (size_t& offset : *offsets_for_adjustment) {
+    if (offset > utf8.length())
+      offset = std::u16string::npos;
+  }
+  OffsetAdjuster::Adjustments adjustments;
+  std::u16string result = UTF8ToUTF16WithAdjustments(utf8, &adjustments);
+  OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment);
+  return result;
+}
+
+std::string UTF16ToUTF8AndAdjustOffsets(
+    const std::u16string_view& utf16,
+    std::vector<size_t>* offsets_for_adjustment) {
+  for (size_t& offset : *offsets_for_adjustment) {
+    if (offset > utf16.length())
+      offset = std::u16string::npos;
+  }
+  std::string result;
+  PrepareForUTF8Output(utf16.data(), utf16.length(), &result);
+  OffsetAdjuster::Adjustments adjustments;
+  ConvertUnicode(utf16.data(), utf16.length(), &result, &adjustments);
+  OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment);
+  return result;
+}
+
+}  // namespace base
diff --git a/src/base/strings/utf_offset_string_conversions.h b/src/base/strings/utf_offset_string_conversions.h
new file mode 100644 (file)
index 0000000..8d6c5e8
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
+#define BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace base {
+
+// A helper class and associated data structures to adjust offsets into a
+// string in response to various adjustments one might do to that string
+// (e.g., eliminating a range).  For details on offsets, see the comments by
+// the AdjustOffsets() function below.
+class OffsetAdjuster {
+ public:
+  struct Adjustment {
+    Adjustment(size_t original_offset,
+               size_t original_length,
+               size_t output_length);
+
+    size_t original_offset;
+    size_t original_length;
+    size_t output_length;
+  };
+  typedef std::vector<Adjustment> Adjustments;
+
+  // Adjusts all offsets in |offsets_for_adjustment| to reflect the adjustments
+  // recorded in |adjustments|.  Adjusted offsets greater than |limit| will be
+  // set to std::u16string::npos.
+  //
+  // Offsets represents insertion/selection points between characters: if |src|
+  // is "abcd", then 0 is before 'a', 2 is between 'b' and 'c', and 4 is at the
+  // end of the string.  Valid input offsets range from 0 to |src_len|.  On
+  // exit, each offset will have been modified to point at the same logical
+  // position in the output string.  If an offset cannot be successfully
+  // adjusted (e.g., because it points into the middle of a multibyte sequence),
+  // it will be set to std::u16string::npos.
+  static void AdjustOffsets(const Adjustments& adjustments,
+                            std::vector<size_t>* offsets_for_adjustment,
+                            size_t limit = std::u16string::npos);
+
+  // Adjusts the single |offset| to reflect the adjustments recorded in
+  // |adjustments|.
+  static void AdjustOffset(const Adjustments& adjustments,
+                           size_t* offset,
+                           size_t limit = std::u16string::npos);
+
+  // Adjusts all offsets in |offsets_for_unadjustment| to reflect the reverse
+  // of the adjustments recorded in |adjustments|.  In other words, the offsets
+  // provided represent offsets into an adjusted string and the caller wants
+  // to know the offsets they correspond to in the original string.  If an
+  // offset cannot be successfully unadjusted (e.g., because it points into
+  // the middle of a multibyte sequence), it will be set to
+  // std::u16string::npos.
+  static void UnadjustOffsets(const Adjustments& adjustments,
+                              std::vector<size_t>* offsets_for_unadjustment);
+
+  // Adjusts the single |offset| to reflect the reverse of the adjustments
+  // recorded in |adjustments|.
+  static void UnadjustOffset(const Adjustments& adjustments, size_t* offset);
+
+  // Combines two sequential sets of adjustments, storing the combined revised
+  // adjustments in |adjustments_on_adjusted_string|.  That is, suppose a
+  // string was altered in some way, with the alterations recorded as
+  // adjustments in |first_adjustments|.  Then suppose the resulting string is
+  // further altered, with the alterations recorded as adjustments scored in
+  // |adjustments_on_adjusted_string|, with the offsets recorded in these
+  // adjustments being with respect to the intermediate string.  This function
+  // combines the two sets of adjustments into one, storing the result in
+  // |adjustments_on_adjusted_string|, whose offsets are correct with respect
+  // to the original string.
+  //
+  // Assumes both parameters are sorted by increasing offset.
+  //
+  // WARNING: Only supports |first_adjustments| that involve collapsing ranges
+  // of text, not expanding ranges.
+  static void MergeSequentialAdjustments(
+      const Adjustments& first_adjustments,
+      Adjustments* adjustments_on_adjusted_string);
+};
+
+// Like the conversions in utf_string_conversions.h, but also fills in an
+// |adjustments| parameter that reflects the alterations done to the string.
+// It may be NULL.
+bool UTF8ToUTF16WithAdjustments(const char* src,
+                                size_t src_len,
+                                std::u16string* output,
+                                base::OffsetAdjuster::Adjustments* adjustments);
+std::u16string UTF8ToUTF16WithAdjustments(
+    const std::string_view& utf8,
+    base::OffsetAdjuster::Adjustments* adjustments);
+// As above, but instead internally examines the adjustments and applies them
+// to |offsets_for_adjustment|.  Input offsets greater than the length of the
+// input string will be set to std::u16string::npos.  See comments by
+// AdjustOffsets().
+std::u16string UTF8ToUTF16AndAdjustOffsets(
+    const std::string_view& utf8,
+    std::vector<size_t>* offsets_for_adjustment);
+std::string UTF16ToUTF8AndAdjustOffsets(
+    const std::u16string_view& utf16,
+    std::vector<size_t>* offsets_for_adjustment);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
diff --git a/src/base/strings/utf_string_conversion_utils.cc b/src/base/strings/utf_string_conversion_utils.cc
new file mode 100644 (file)
index 0000000..32aa3e3
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/utf_string_conversion_utils.h"
+
+#include "base/third_party/icu/icu_utf.h"
+#include "util/build_config.h"
+
+namespace base {
+
+// ReadUnicodeCharacter --------------------------------------------------------
+
+bool ReadUnicodeCharacter(const char* src,
+                          int32_t src_len,
+                          int32_t* char_index,
+                          uint32_t* code_point_out) {
+  // U8_NEXT expects to be able to use -1 to signal an error, so we must
+  // use a signed type for code_point.  But this function returns false
+  // on error anyway, so code_point_out is unsigned.
+  int32_t code_point;
+  CBU8_NEXT(src, *char_index, src_len, code_point);
+  *code_point_out = static_cast<uint32_t>(code_point);
+
+  // The ICU macro above moves to the next char, we want to point to the last
+  // char consumed.
+  (*char_index)--;
+
+  // Validate the decoded value.
+  return IsValidCodepoint(code_point);
+}
+
+bool ReadUnicodeCharacter(const char16_t* src,
+                          int32_t src_len,
+                          int32_t* char_index,
+                          uint32_t* code_point) {
+  if (CBU16_IS_SURROGATE(src[*char_index])) {
+    if (!CBU16_IS_SURROGATE_LEAD(src[*char_index]) ||
+        *char_index + 1 >= src_len || !CBU16_IS_TRAIL(src[*char_index + 1])) {
+      // Invalid surrogate pair.
+      return false;
+    }
+
+    // Valid surrogate pair.
+    *code_point =
+        CBU16_GET_SUPPLEMENTARY(src[*char_index], src[*char_index + 1]);
+    (*char_index)++;
+  } else {
+    // Not a surrogate, just one 16-bit word.
+    *code_point = src[*char_index];
+  }
+
+  return IsValidCodepoint(*code_point);
+}
+
+// WriteUnicodeCharacter -------------------------------------------------------
+
+size_t WriteUnicodeCharacter(uint32_t code_point, std::string* output) {
+  if (code_point <= 0x7f) {
+    // Fast path the common case of one byte.
+    output->push_back(static_cast<char>(code_point));
+    return 1;
+  }
+
+  // CBU8_APPEND_UNSAFE can append up to 4 bytes.
+  size_t char_offset = output->length();
+  size_t original_char_offset = char_offset;
+  output->resize(char_offset + CBU8_MAX_LENGTH);
+
+  CBU8_APPEND_UNSAFE(&(*output)[0], char_offset, code_point);
+
+  // CBU8_APPEND_UNSAFE will advance our pointer past the inserted character, so
+  // it will represent the new length of the string.
+  output->resize(char_offset);
+  return char_offset - original_char_offset;
+}
+
+size_t WriteUnicodeCharacter(uint32_t code_point, std::u16string* output) {
+  if (CBU16_LENGTH(code_point) == 1) {
+    // Thie code point is in the Basic Multilingual Plane (BMP).
+    output->push_back(static_cast<char16_t>(code_point));
+    return 1;
+  }
+  // Non-BMP characters use a double-character encoding.
+  size_t char_offset = output->length();
+  output->resize(char_offset + CBU16_MAX_LENGTH);
+  CBU16_APPEND_UNSAFE(&(*output)[0], char_offset, code_point);
+  return CBU16_MAX_LENGTH;
+}
+
+// Generalized Unicode converter -----------------------------------------------
+
+template <typename CHAR>
+void PrepareForUTF8Output(const CHAR* src,
+                          size_t src_len,
+                          std::string* output) {
+  output->clear();
+  if (src_len == 0)
+    return;
+  if (src[0] < 0x80) {
+    // Assume that the entire input will be ASCII.
+    output->reserve(src_len);
+  } else {
+    // Assume that the entire input is non-ASCII and will have 3 bytes per char.
+    output->reserve(src_len * 3);
+  }
+}
+
+// Instantiate versions we know callers will need.
+template void PrepareForUTF8Output(const char16_t*, size_t, std::string*);
+
+template <typename STRING>
+void PrepareForUTF16Or32Output(const char* src,
+                               size_t src_len,
+                               STRING* output) {
+  output->clear();
+  if (src_len == 0)
+    return;
+  if (static_cast<unsigned char>(src[0]) < 0x80) {
+    // Assume the input is all ASCII, which means 1:1 correspondence.
+    output->reserve(src_len);
+  } else {
+    // Otherwise assume that the UTF-8 sequences will have 2 bytes for each
+    // character.
+    output->reserve(src_len / 2);
+  }
+}
+
+// Instantiate versions we know callers will need.
+template void PrepareForUTF16Or32Output(const char*, size_t, std::u16string*);
+
+}  // namespace base
diff --git a/src/base/strings/utf_string_conversion_utils.h b/src/base/strings/utf_string_conversion_utils.h
new file mode 100644 (file)
index 0000000..e24a7db
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_
+#define BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_
+
+// Low-level UTF handling functions. Most code will want to use the functions
+// in utf_string_conversions.h
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+namespace base {
+
+inline bool IsValidCodepoint(uint32_t code_point) {
+  // Excludes the surrogate code points ([0xD800, 0xDFFF]) and
+  // codepoints larger than 0x10FFFF (the highest codepoint allowed).
+  // Non-characters and unassigned codepoints are allowed.
+  return code_point < 0xD800u ||
+         (code_point >= 0xE000u && code_point <= 0x10FFFFu);
+}
+
+inline bool IsValidCharacter(uint32_t code_point) {
+  // Excludes non-characters (U+FDD0..U+FDEF, and all codepoints ending in
+  // 0xFFFE or 0xFFFF) from the set of valid code points.
+  return code_point < 0xD800u ||
+         (code_point >= 0xE000u && code_point < 0xFDD0u) ||
+         (code_point > 0xFDEFu && code_point <= 0x10FFFFu &&
+          (code_point & 0xFFFEu) != 0xFFFEu);
+}
+
+// ReadUnicodeCharacter --------------------------------------------------------
+
+// Reads a UTF-8 stream, placing the next code point into the given output
+// |*code_point|. |src| represents the entire string to read, and |*char_index|
+// is the character offset within the string to start reading at. |*char_index|
+// will be updated to index the last character read, such that incrementing it
+// (as in a for loop) will take the reader to the next character.
+//
+// Returns true on success. On false, |*code_point| will be invalid.
+bool ReadUnicodeCharacter(const char* src,
+                          int32_t src_len,
+                          int32_t* char_index,
+                          uint32_t* code_point_out);
+
+// Reads a UTF-16 character. The usage is the same as the 8-bit version above.
+bool ReadUnicodeCharacter(const char16_t* src,
+                          int32_t src_len,
+                          int32_t* char_index,
+                          uint32_t* code_point);
+
+// WriteUnicodeCharacter -------------------------------------------------------
+
+// Appends a UTF-8 character to the given 8-bit string.  Returns the number of
+// bytes written.
+size_t WriteUnicodeCharacter(uint32_t code_point, std::string* output);
+
+// Appends the given code point as a UTF-16 character to the given 16-bit
+// string.  Returns the number of 16-bit values written.
+size_t WriteUnicodeCharacter(uint32_t code_point, std::u16string* output);
+
+// Generalized Unicode converter -----------------------------------------------
+
+// Guesses the length of the output in UTF-8 in bytes, clears that output
+// string, and reserves that amount of space.  We assume that the input
+// character types are unsigned, which will be true for UTF-16 and -32 on our
+// systems.
+template <typename CHAR>
+void PrepareForUTF8Output(const CHAR* src, size_t src_len, std::string* output);
+
+// Prepares an output buffer (containing either UTF-16 or -32 data) given some
+// UTF-8 input that will be converted to it.  See PrepareForUTF8Output().
+template <typename STRING>
+void PrepareForUTF16Or32Output(const char* src, size_t src_len, STRING* output);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_UTF_STRING_CONVERSION_UTILS_H_
diff --git a/src/base/strings/utf_string_conversions.cc b/src/base/strings/utf_string_conversions.cc
new file mode 100644 (file)
index 0000000..4b4e420
--- /dev/null
@@ -0,0 +1,195 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/utf_string_conversions.h"
+
+#include <stdint.h>
+
+#include <string_view>
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "util/build_config.h"
+
+namespace base {
+
+namespace {
+
+constexpr int32_t kErrorCodePoint = 0xFFFD;
+
+// Size coefficient ----------------------------------------------------------
+// The maximum number of codeunits in the destination encoding corresponding to
+// one codeunit in the source encoding.
+
+template <typename SrcChar, typename DestChar>
+struct SizeCoefficient {
+  static_assert(sizeof(SrcChar) < sizeof(DestChar),
+                "Default case: from a smaller encoding to the bigger one");
+
+  // ASCII symbols are encoded by one codeunit in all encodings.
+  static constexpr int value = 1;
+};
+
+template <>
+struct SizeCoefficient<char16_t, char> {
+  // One UTF-16 codeunit corresponds to at most 3 codeunits in UTF-8.
+  static constexpr int value = 3;
+};
+
+template <typename SrcChar, typename DestChar>
+constexpr int size_coefficient_v =
+    SizeCoefficient<std::decay_t<SrcChar>, std::decay_t<DestChar>>::value;
+
+// UnicodeAppendUnsafe --------------------------------------------------------
+// Function overloads that write code_point to the output string. Output string
+// has to have enough space for the codepoint.
+
+void UnicodeAppendUnsafe(char* out, int32_t* size, uint32_t code_point) {
+  CBU8_APPEND_UNSAFE(out, *size, code_point);
+}
+
+void UnicodeAppendUnsafe(char16_t* out, int32_t* size, uint32_t code_point) {
+  CBU16_APPEND_UNSAFE(out, *size, code_point);
+}
+
+// DoUTFConversion ------------------------------------------------------------
+// Main driver of UTFConversion specialized for different Src encodings.
+// dest has to have enough room for the converted text.
+
+template <typename DestChar>
+bool DoUTFConversion(const char* src,
+                     int32_t src_len,
+                     DestChar* dest,
+                     int32_t* dest_len) {
+  bool success = true;
+
+  for (int32_t i = 0; i < src_len;) {
+    int32_t code_point;
+    CBU8_NEXT(src, i, src_len, code_point);
+
+    if (!IsValidCodepoint(code_point)) {
+      success = false;
+      code_point = kErrorCodePoint;
+    }
+
+    UnicodeAppendUnsafe(dest, dest_len, code_point);
+  }
+
+  return success;
+}
+
+template <typename DestChar>
+bool DoUTFConversion(const char16_t* src,
+                     int32_t src_len,
+                     DestChar* dest,
+                     int32_t* dest_len) {
+  bool success = true;
+
+  auto ConvertSingleChar = [&success](char16_t in) -> int32_t {
+    if (!CBU16_IS_SINGLE(in) || !IsValidCodepoint(in)) {
+      success = false;
+      return kErrorCodePoint;
+    }
+    return in;
+  };
+
+  int32_t i = 0;
+
+  // Always have another symbol in order to avoid checking boundaries in the
+  // middle of the surrogate pair.
+  while (i < src_len - 1) {
+    int32_t code_point;
+
+    if (CBU16_IS_LEAD(src[i]) && CBU16_IS_TRAIL(src[i + 1])) {
+      code_point = CBU16_GET_SUPPLEMENTARY(src[i], src[i + 1]);
+      if (!IsValidCodepoint(code_point)) {
+        code_point = kErrorCodePoint;
+        success = false;
+      }
+      i += 2;
+    } else {
+      code_point = ConvertSingleChar(src[i]);
+      ++i;
+    }
+
+    UnicodeAppendUnsafe(dest, dest_len, code_point);
+  }
+
+  if (i < src_len)
+    UnicodeAppendUnsafe(dest, dest_len, ConvertSingleChar(src[i]));
+
+  return success;
+}
+
+// UTFConversion --------------------------------------------------------------
+// Function template for generating all UTF conversions.
+
+template <typename InputString, typename DestString>
+bool UTFConversion(const InputString& src_str, DestString* dest_str) {
+  if (IsStringASCII(src_str)) {
+    dest_str->assign(src_str.begin(), src_str.end());
+    return true;
+  }
+
+  dest_str->resize(src_str.length() *
+                   size_coefficient_v<typename InputString::value_type,
+                                      typename DestString::value_type>);
+
+  // Empty string is ASCII => it OK to call operator[].
+  auto* dest = &(*dest_str)[0];
+
+  // ICU requires 32 bit numbers.
+  int32_t src_len32 = static_cast<int32_t>(src_str.length());
+  int32_t dest_len32 = 0;
+
+  bool res = DoUTFConversion(src_str.data(), src_len32, dest, &dest_len32);
+
+  dest_str->resize(dest_len32);
+  dest_str->shrink_to_fit();
+
+  return res;
+}
+
+}  // namespace
+
+// UTF16 <-> UTF8 --------------------------------------------------------------
+
+bool UTF8ToUTF16(const char* src, size_t src_len, std::u16string* output) {
+  return UTFConversion(std::string_view(src, src_len), output);
+}
+
+std::u16string UTF8ToUTF16(std::string_view utf8) {
+  std::u16string ret;
+  // Ignore the success flag of this call, it will do the best it can for
+  // invalid input, which is what we want here.
+  UTF8ToUTF16(utf8.data(), utf8.size(), &ret);
+  return ret;
+}
+
+bool UTF16ToUTF8(const char16_t* src, size_t src_len, std::string* output) {
+  return UTFConversion(std::u16string_view(src, src_len), output);
+}
+
+std::string UTF16ToUTF8(std::u16string_view utf16) {
+  std::string ret;
+  // Ignore the success flag of this call, it will do the best it can for
+  // invalid input, which is what we want here.
+  UTF16ToUTF8(utf16.data(), utf16.length(), &ret);
+  return ret;
+}
+
+// ASCII <-> UTF-16 -----------------------------------------------------------
+
+std::u16string ASCIIToUTF16(std::string_view ascii) {
+  DCHECK(IsStringASCII(ascii)) << ascii;
+  return std::u16string(ascii.begin(), ascii.end());
+}
+
+std::string UTF16ToASCII(std::u16string_view utf16) {
+  DCHECK(IsStringASCII(utf16)) << UTF16ToUTF8(utf16);
+  return std::string(utf16.begin(), utf16.end());
+}
+
+}  // namespace base
diff --git a/src/base/strings/utf_string_conversions.h b/src/base/strings/utf_string_conversions.h
new file mode 100644 (file)
index 0000000..116e6a5
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_UTF_STRING_CONVERSIONS_H_
+#define BASE_STRINGS_UTF_STRING_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <string_view>
+
+namespace base {
+
+bool UTF8ToUTF16(const char* src, size_t src_len, std::u16string* output);
+std::u16string UTF8ToUTF16(std::string_view utf8);
+bool UTF16ToUTF8(const char16_t* src, size_t src_len, std::string* output);
+std::string UTF16ToUTF8(std::u16string_view utf16);
+
+// This converts an ASCII string, typically a hardcoded constant, to a UTF16
+// string.
+std::u16string ASCIIToUTF16(std::string_view ascii);
+
+// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
+// beforehand.
+std::string UTF16ToASCII(std::u16string_view utf16);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_UTF_STRING_CONVERSIONS_H_
diff --git a/src/base/sys_byteorder.h b/src/base/sys_byteorder.h
new file mode 100644 (file)
index 0000000..bb7b4c3
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header defines cross-platform ByteSwap() implementations for 16, 32 and
+// 64-bit values, and NetToHostXX() / HostToNextXX() functions equivalent to
+// the traditional ntohX() and htonX() functions.
+// Use the functions defined here rather than using the platform-specific
+// functions directly.
+
+#ifndef BASE_SYS_BYTEORDER_H_
+#define BASE_SYS_BYTEORDER_H_
+
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "util/build_config.h"
+
+#if defined(COMPILER_MSVC)
+#include <stdlib.h>
+#endif
+
+namespace base {
+
+// Returns a value with all bytes in |x| swapped, i.e. reverses the endianness.
+inline uint16_t ByteSwap(uint16_t x) {
+#if defined(COMPILER_MSVC)
+  return _byteswap_ushort(x);
+#else
+  return __builtin_bswap16(x);
+#endif
+}
+
+inline uint32_t ByteSwap(uint32_t x) {
+#if defined(COMPILER_MSVC)
+  return _byteswap_ulong(x);
+#else
+  return __builtin_bswap32(x);
+#endif
+}
+
+inline uint64_t ByteSwap(uint64_t x) {
+#if defined(COMPILER_MSVC)
+  return _byteswap_uint64(x);
+#else
+  return __builtin_bswap64(x);
+#endif
+}
+
+inline uintptr_t ByteSwapUintPtrT(uintptr_t x) {
+  // We do it this way because some build configurations are ILP32 even when
+  // defined(ARCH_CPU_64_BITS). Unfortunately, we can't use sizeof in #ifs. But,
+  // because these conditionals are constexprs, the irrelevant branches will
+  // likely be optimized away, so this construction should not result in code
+  // bloat.
+  if (sizeof(uintptr_t) == 4) {
+    return ByteSwap(static_cast<uint32_t>(x));
+  } else if (sizeof(uintptr_t) == 8) {
+    return ByteSwap(static_cast<uint64_t>(x));
+  } else {
+    NOTREACHED();
+  }
+}
+
+// Converts the bytes in |x| from host order (endianness) to little endian, and
+// returns the result.
+inline uint16_t ByteSwapToLE16(uint16_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return x;
+#else
+  return ByteSwap(x);
+#endif
+}
+inline uint32_t ByteSwapToLE32(uint32_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return x;
+#else
+  return ByteSwap(x);
+#endif
+}
+inline uint64_t ByteSwapToLE64(uint64_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return x;
+#else
+  return ByteSwap(x);
+#endif
+}
+
+// Converts the bytes in |x| from network to host order (endianness), and
+// returns the result.
+inline uint16_t NetToHost16(uint16_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+inline uint32_t NetToHost32(uint32_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+inline uint64_t NetToHost64(uint64_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+
+// Converts the bytes in |x| from host to network order (endianness), and
+// returns the result.
+inline uint16_t HostToNet16(uint16_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+inline uint32_t HostToNet32(uint32_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+inline uint64_t HostToNet64(uint64_t x) {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  return ByteSwap(x);
+#else
+  return x;
+#endif
+}
+
+}  // namespace base
+
+#endif  // BASE_SYS_BYTEORDER_H_
diff --git a/src/base/template_util.h b/src/base/template_util.h
new file mode 100644 (file)
index 0000000..41028c4
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEMPLATE_UTIL_H_
+#define BASE_TEMPLATE_UTIL_H_
+
+#include <iosfwd>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+namespace base {
+
+namespace internal {
+
+// Uses expression SFINAE to detect whether using operator<< would work.
+template <typename T, typename = void>
+struct SupportsOstreamOperator : std::false_type {};
+template <typename T>
+struct SupportsOstreamOperator<T,
+                               decltype(void(std::declval<std::ostream&>()
+                                             << std::declval<T>()))>
+    : std::true_type {};
+
+// Used to detech whether the given type is an iterator.  This is normally used
+// with std::enable_if to provide disambiguation for functions that take
+// templatzed iterators as input.
+template <typename T, typename = void>
+struct is_iterator : std::false_type {};
+
+template <typename T>
+struct is_iterator<
+    T,
+    std::void_t<typename std::iterator_traits<T>::iterator_category>>
+    : std::true_type {};
+
+}  // namespace internal
+
+}  // namespace base
+
+#endif  // BASE_TEMPLATE_UTIL_H_
diff --git a/src/base/third_party/icu/LICENSE b/src/base/third_party/icu/LICENSE
new file mode 100644 (file)
index 0000000..2882e4e
--- /dev/null
@@ -0,0 +1,76 @@
+COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
+
+Copyright © 1991-2017 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in http://www.unicode.org/copyright.html
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that either
+(a) this copyright and permission notice appear with all copies
+of the Data Files or Software, or
+(b) this copyright and permission notice appear in associated
+Documentation.
+
+THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
+
+---------------------
+
+Third-Party Software Licenses
+
+This section contains third-party software notices and/or additional
+terms for licensed third-party software components included within ICU
+libraries.
+
+1. ICU License - ICU 1.8.1 to ICU 57.1
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2016 International Business Machines Corporation and others
+All rights reserved.
+
+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, and/or sell copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies of
+the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+
+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
+OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
+SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale, use
+or other dealings in this Software without prior written authorization
+of the copyright holder.
+
+All trademarks and registered trademarks mentioned herein are the
+property of their respective owners.
diff --git a/src/base/third_party/icu/README.chromium b/src/base/third_party/icu/README.chromium
new file mode 100644 (file)
index 0000000..297e89a
--- /dev/null
@@ -0,0 +1,17 @@
+Name: ICU
+URL: http://site.icu-project.org/
+Version: 60
+License: Unicode
+License File: NOT_SHIPPED
+
+This file has the relevant components from ICU copied to handle basic UTF8/16/32
+conversions. Components are copied from umachine.h, utf.h, utf8.h, and utf16.h
+into icu_utf.h, and from utf_impl.cpp into icu_utf.cc.
+
+The main change is that U_/U8_/U16_ prefixes have been replaced with
+CBU_/CBU8_/CBU16_ (for "Chrome Base") to avoid confusion with the "real" ICU
+macros should ICU be in use on the system. For the same reason, the functions
+and types have been put in the "base_icu" namespace.
+
+Note that this license file is marked as NOT_SHIPPED, since a more complete
+ICU license is included from //third_party/icu/README.chromium
diff --git a/src/base/third_party/icu/icu_utf.cc b/src/base/third_party/icu/icu_utf.cc
new file mode 100644 (file)
index 0000000..8dcb401
--- /dev/null
@@ -0,0 +1,129 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+******************************************************************************
+*
+*   Copyright (C) 1999-2012, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*
+******************************************************************************
+*   file name:  utf_impl.cpp
+*   encoding:   UTF-8
+*   tab size:   8 (not used)
+*   indentation:4
+*
+*   created on: 1999sep13
+*   created by: Markus W. Scherer
+*
+*   This file provides implementation functions for macros in the utfXX.h
+*   that would otherwise be too long as macros.
+*/
+
+#include "base/third_party/icu/icu_utf.h"
+
+namespace base_icu {
+
+// source/common/utf_impl.cpp
+
+static const UChar32 utf8_errorValue[6] = {
+    // Same values as UTF8_ERROR_VALUE_1, UTF8_ERROR_VALUE_2, UTF_ERROR_VALUE,
+    // but without relying on the obsolete unicode/utf_old.h.
+    0x15, 0x9f, 0xffff, 0x10ffff};
+
+static UChar32 errorValue(int32_t count, int8_t strict) {
+  if (strict >= 0) {
+    return utf8_errorValue[count];
+  } else if (strict == -3) {
+    return 0xfffd;
+  } else {
+    return CBU_SENTINEL;
+  }
+}
+
+/*
+ * Handle the non-inline part of the U8_NEXT() and U8_NEXT_FFFD() macros
+ * and their obsolete sibling UTF8_NEXT_CHAR_SAFE().
+ *
+ * U8_NEXT() supports NUL-terminated strings indicated via length<0.
+ *
+ * The "strict" parameter controls the error behavior:
+ * <0  "Safe" behavior of U8_NEXT():
+ *     -1: All illegal byte sequences yield U_SENTINEL=-1.
+ *     -2: Same as -1, except for lenient treatment of surrogate code points as
+ * legal. Some implementations use this for roundtripping of Unicode 16-bit
+ * strings that are not well-formed UTF-16, that is, they contain
+ * unpaired surrogates. -3: All illegal byte sequences yield U+FFFD. 0
+ * Obsolete "safe" behavior of UTF8_NEXT_CHAR_SAFE(..., FALSE): All illegal
+ * byte sequences yield a positive code point such that this result code
+ * point would be encoded with the same number of bytes as the illegal
+ * sequence. >0  Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(...,
+ * TRUE): Same as the obsolete "safe" behavior, but non-characters are also
+ * treated like illegal sequences.
+ *
+ * Note that a UBool is the same as an int8_t.
+ */
+UChar32 utf8_nextCharSafeBody(const uint8_t* s,
+                              int32_t* pi,
+                              int32_t length,
+                              UChar32 c,
+                              UBool strict) {
+  // *pi is one after byte c.
+  int32_t i = *pi;
+  // length can be negative for NUL-terminated strings: Read and validate one
+  // byte at a time.
+  if (i == length || c > 0xf4) {
+    // end of string, or not a lead byte
+  } else if (c >= 0xf0) {
+    // Test for 4-byte sequences first because
+    // U8_NEXT() handles shorter valid sequences inline.
+    uint8_t t1 = s[i], t2, t3;
+    c &= 7;
+    if (CBU8_IS_VALID_LEAD4_AND_T1(c, t1) && ++i != length &&
+        (t2 = s[i] - 0x80) <= 0x3f && ++i != length &&
+        (t3 = s[i] - 0x80) <= 0x3f) {
+      ++i;
+      c = (c << 18) | ((t1 & 0x3f) << 12) | (t2 << 6) | t3;
+      // strict: forbid non-characters like U+fffe
+      if (strict <= 0 || !CBU_IS_UNICODE_NONCHAR(c)) {
+        *pi = i;
+        return c;
+      }
+    }
+  } else if (c >= 0xe0) {
+    c &= 0xf;
+    if (strict != -2) {
+      uint8_t t1 = s[i], t2;
+      if (CBU8_IS_VALID_LEAD3_AND_T1(c, t1) && ++i != length &&
+          (t2 = s[i] - 0x80) <= 0x3f) {
+        ++i;
+        c = (c << 12) | ((t1 & 0x3f) << 6) | t2;
+        // strict: forbid non-characters like U+fffe
+        if (strict <= 0 || !CBU_IS_UNICODE_NONCHAR(c)) {
+          *pi = i;
+          return c;
+        }
+      }
+    } else {
+      // strict=-2 -> lenient: allow surrogates
+      uint8_t t1 = s[i] - 0x80, t2;
+      if (t1 <= 0x3f && (c > 0 || t1 >= 0x20) && ++i != length &&
+          (t2 = s[i] - 0x80) <= 0x3f) {
+        *pi = i + 1;
+        return (c << 12) | (t1 << 6) | t2;
+      }
+    }
+  } else if (c >= 0xc2) {
+    uint8_t t1 = s[i] - 0x80;
+    if (t1 <= 0x3f) {
+      *pi = i + 1;
+      return ((c - 0xc0) << 6) | t1;
+    }
+  }  // else 0x80<=c<0xc2 is not a lead byte
+
+  /* error handling */
+  c = errorValue(i - *pi, strict);
+  *pi = i;
+  return c;
+}
+
+}  // namespace base_icu
diff --git a/src/base/third_party/icu/icu_utf.h b/src/base/third_party/icu/icu_utf.h
new file mode 100644 (file)
index 0000000..b626b39
--- /dev/null
@@ -0,0 +1,464 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+******************************************************************************
+*
+*   Copyright (C) 1999-2015, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*
+******************************************************************************
+*/
+
+#ifndef BASE_THIRD_PARTY_ICU_ICU_UTF_H_
+#define BASE_THIRD_PARTY_ICU_ICU_UTF_H_
+
+#include <stdint.h>
+
+namespace base_icu {
+
+// source/common/unicode/umachine.h
+
+/** The ICU boolean type @stable ICU 2.0 */
+typedef int8_t UBool;
+
+/**
+ * Define UChar32 as a type for single Unicode code points.
+ * UChar32 is a signed 32-bit integer (same as int32_t).
+ *
+ * The Unicode code point range is 0..0x10ffff.
+ * All other values (negative or >=0x110000) are illegal as Unicode code points.
+ * They may be used as sentinel values to indicate "done", "error"
+ * or similar non-code point conditions.
+ *
+ * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined
+ * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned)
+ * or else to be uint32_t.
+ * That is, the definition of UChar32 was platform-dependent.
+ *
+ * @see U_SENTINEL
+ * @stable ICU 2.4
+ */
+typedef int32_t UChar32;
+
+/**
+ * This value is intended for sentinel values for APIs that
+ * (take or) return single code points (UChar32).
+ * It is outside of the Unicode code point range 0..0x10ffff.
+ *
+ * For example, a "done" or "error" value in a new API
+ * could be indicated with U_SENTINEL.
+ *
+ * ICU APIs designed before ICU 2.4 usually define service-specific "done"
+ * values, mostly 0xffff.
+ * Those may need to be distinguished from
+ * actual U+ffff text contents by calling functions like
+ * CharacterIterator::hasNext() or UnicodeString::length().
+ *
+ * @return -1
+ * @see UChar32
+ * @stable ICU 2.4
+ */
+#define CBU_SENTINEL (-1)
+
+// source/common/unicode/utf.h
+
+/**
+ * Is this code point a Unicode noncharacter?
+ * @param c 32-bit code point
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU_IS_UNICODE_NONCHAR(c)                                \
+  ((c) >= 0xfdd0 && ((c) <= 0xfdef || ((c)&0xfffe) == 0xfffe) && \
+   (c) <= 0x10ffff)
+
+/**
+ * Is c a Unicode code point value (0..U+10ffff)
+ * that can be assigned a character?
+ *
+ * Code points that are not characters include:
+ * - single surrogate code points (U+d800..U+dfff, 2048 code points)
+ * - the last two code points on each plane (U+__fffe and U+__ffff, 34 code
+ * points) - U+fdd0..U+fdef (new with Unicode 3.1, 32 code points) - the highest
+ * Unicode code point value is U+10ffff
+ *
+ * This means that all code points below U+d800 are character code points,
+ * and that boundary is tested first for performance.
+ *
+ * @param c 32-bit code point
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU_IS_UNICODE_CHAR(c) \
+  ((uint32_t)(c) < 0xd800 ||   \
+   (0xdfff < (c) && (c) <= 0x10ffff && !CBU_IS_UNICODE_NONCHAR(c)))
+
+/**
+ * Is this code point a surrogate (U+d800..U+dfff)?
+ * @param c 32-bit code point
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU_IS_SURROGATE(c) (((c)&0xfffff800) == 0xd800)
+
+/**
+ * Assuming c is a surrogate code point (U_IS_SURROGATE(c)),
+ * is it a lead surrogate?
+ * @param c 32-bit code point
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU_IS_SURROGATE_LEAD(c) (((c)&0x400) == 0)
+
+// source/common/unicode/utf8.h
+
+/**
+ * Internal bit vector for 3-byte UTF-8 validity check, for use in
+ * U8_IS_VALID_LEAD3_AND_T1. Each bit indicates whether one lead byte + first
+ * trail byte pair starts a valid sequence. Lead byte E0..EF bits 3..0 are used
+ * as byte index, first trail byte bits 7..5 are used as bit index into that
+ * byte.
+ * @see U8_IS_VALID_LEAD3_AND_T1
+ * @internal
+ */
+#define CBU8_LEAD3_T1_BITS \
+  "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30"
+
+/**
+ * Internal 3-byte UTF-8 validity check.
+ * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid
+ * sequence.
+ * @internal
+ */
+#define CBU8_IS_VALID_LEAD3_AND_T1(lead, t1) \
+  (CBU8_LEAD3_T1_BITS[(lead)&0xf] & (1 << ((uint8_t)(t1) >> 5)))
+
+/**
+ * Internal bit vector for 4-byte UTF-8 validity check, for use in
+ * U8_IS_VALID_LEAD4_AND_T1. Each bit indicates whether one lead byte + first
+ * trail byte pair starts a valid sequence. First trail byte bits 7..4 are used
+ * as byte index, lead byte F0..F4 bits 2..0 are used as bit index into that
+ * byte.
+ * @see U8_IS_VALID_LEAD4_AND_T1
+ * @internal
+ */
+#define CBU8_LEAD4_T1_BITS \
+  "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00"
+
+/**
+ * Internal 4-byte UTF-8 validity check.
+ * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid
+ * sequence.
+ * @internal
+ */
+#define CBU8_IS_VALID_LEAD4_AND_T1(lead, t1) \
+  (CBU8_LEAD4_T1_BITS[(uint8_t)(t1) >> 4] & (1 << ((lead)&7)))
+
+/**
+ * Function for handling "next code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clie
+nts;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros i
+n this
+ * file and thus must remain stable, and should not be hidden when other interna
+l
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+UChar32 utf8_nextCharSafeBody(const uint8_t* s,
+                              int32_t* pi,
+                              int32_t length,
+                              ::base_icu::UChar32 c,
+                              ::base_icu::UBool strict);
+
+/**
+ * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)?
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU8_IS_SINGLE(c) (((c)&0x80) == 0)
+
+/**
+ * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4)
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU8_IS_LEAD(c) ((uint8_t)((c)-0xc2) <= 0x32)
+
+/**
+ * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF)
+ * @param c 8-bit code unit (byte)
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU8_IS_TRAIL(c) ((int8_t)(c) < -0x40)
+
+/**
+ * How many code units (bytes) are used for the UTF-8 encoding
+ * of this Unicode code point?
+ * @param c 32-bit code point
+ * @return 1..4, or 0 if c is a surrogate or not a Unicode code point
+ * @stable ICU 2.4
+ */
+#define CBU8_LENGTH(c)                                                      \
+  ((uint32_t)(c) <= 0x7f                                                    \
+       ? 1                                                                  \
+       : ((uint32_t)(c) <= 0x7ff                                            \
+              ? 2                                                           \
+              : ((uint32_t)(c) <= 0xd7ff                                    \
+                     ? 3                                                    \
+                     : ((uint32_t)(c) <= 0xdfff || (uint32_t)(c) > 0x10ffff \
+                            ? 0                                             \
+                            : ((uint32_t)(c) <= 0xffff ? 3 : 4)))))
+
+/**
+ * The maximum number of UTF-8 code units (bytes) per Unicode code point
+ * (U+0000..U+10ffff).
+ * @return 4
+ * @stable ICU 2.4
+ */
+#define CBU8_MAX_LENGTH 4
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, checks for illegal sequences and for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead byte of a multi-byte sequence,
+ * in which case the macro will read the whole sequence.
+ * If the offset points to a trail byte or an illegal UTF-8 sequence, then
+ * c is set to a negative value.
+ *
+ * @param s const uint8_t * string
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
+ * @param c output UChar32 variable, set to <0 in case of an error
+ * @see U8_NEXT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define CBU8_NEXT(s, i, length, c)                                       \
+  {                                                                      \
+    (c) = (uint8_t)(s)[(i)++];                                           \
+    if (!CBU8_IS_SINGLE(c)) {                                            \
+      uint8_t __t1, __t2;                                                \
+      if (/* handle U+0800..U+FFFF inline */                             \
+          (0xe0 <= (c) && (c) < 0xf0) &&                                 \
+          (((i) + 1) < (length) || (length) < 0) &&                      \
+          CBU8_IS_VALID_LEAD3_AND_T1((c), __t1 = (s)[i]) &&              \
+          (__t2 = (s)[(i) + 1] - 0x80) <= 0x3f) {                        \
+        (c) = (((c)&0xf) << 12) | ((__t1 & 0x3f) << 6) | __t2;           \
+        (i) += 2;                                                        \
+      } else if (/* handle U+0080..U+07FF inline */                      \
+                 ((c) < 0xe0 && (c) >= 0xc2) && ((i) != (length)) &&     \
+                 (__t1 = (s)[i] - 0x80) <= 0x3f) {                       \
+        (c) = (((c)&0x1f) << 6) | __t1;                                  \
+        ++(i);                                                           \
+      } else {                                                           \
+        /* function call for "complicated" and error cases */            \
+        (c) = ::base_icu::utf8_nextCharSafeBody((const uint8_t*)s, &(i), \
+                                                (length), c, -1);        \
+      }                                                                  \
+    }                                                                    \
+  }
+
+/**
+ * Append a code point to a string, overwriting 1 to 4 bytes.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Unsafe" macro, assumes a valid code point and sufficient space in the
+ * string. Otherwise, the result is undefined.
+ *
+ * @param s const uint8_t * string buffer
+ * @param i string offset
+ * @param c code point to append
+ * @see U8_APPEND
+ * @stable ICU 2.4
+ */
+#define CBU8_APPEND_UNSAFE(s, i, c)                            \
+  {                                                            \
+    if ((uint32_t)(c) <= 0x7f) {                               \
+      (s)[(i)++] = (uint8_t)(c);                               \
+    } else {                                                   \
+      if ((uint32_t)(c) <= 0x7ff) {                            \
+        (s)[(i)++] = (uint8_t)(((c) >> 6) | 0xc0);             \
+      } else {                                                 \
+        if ((uint32_t)(c) <= 0xffff) {                         \
+          (s)[(i)++] = (uint8_t)(((c) >> 12) | 0xe0);          \
+        } else {                                               \
+          (s)[(i)++] = (uint8_t)(((c) >> 18) | 0xf0);          \
+          (s)[(i)++] = (uint8_t)((((c) >> 12) & 0x3f) | 0x80); \
+        }                                                      \
+        (s)[(i)++] = (uint8_t)((((c) >> 6) & 0x3f) | 0x80);    \
+      }                                                        \
+      (s)[(i)++] = (uint8_t)(((c)&0x3f) | 0x80);               \
+    }                                                          \
+  }
+
+// source/common/unicode/utf16.h
+
+/**
+ * Does this code unit alone encode a code point (BMP, not a surrogate)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU16_IS_SINGLE(c) !CBU_IS_SURROGATE(c)
+
+/**
+ * Is this code unit a lead surrogate (U+d800..U+dbff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU16_IS_LEAD(c) (((c)&0xfffffc00) == 0xd800)
+
+/**
+ * Is this code unit a trail surrogate (U+dc00..U+dfff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU16_IS_TRAIL(c) (((c)&0xfffffc00) == 0xdc00)
+
+/**
+ * Is this code unit a surrogate (U+d800..U+dfff)?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU16_IS_SURROGATE(c) CBU_IS_SURROGATE(c)
+
+/**
+ * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)),
+ * is it a lead surrogate?
+ * @param c 16-bit code unit
+ * @return TRUE or FALSE
+ * @stable ICU 2.4
+ */
+#define CBU16_IS_SURROGATE_LEAD(c) (((c)&0x400) == 0)
+
+/**
+ * Helper constant for U16_GET_SUPPLEMENTARY.
+ * @internal
+ */
+#define CBU16_SURROGATE_OFFSET ((0xd800 << 10UL) + 0xdc00 - 0x10000)
+
+/**
+ * Get a supplementary code point value (U+10000..U+10ffff)
+ * from its lead and trail surrogates.
+ * The result is undefined if the input values are not
+ * lead and trail surrogates.
+ *
+ * @param lead lead surrogate (U+d800..U+dbff)
+ * @param trail trail surrogate (U+dc00..U+dfff)
+ * @return supplementary code point (U+10000..U+10ffff)
+ * @stable ICU 2.4
+ */
+#define CBU16_GET_SUPPLEMENTARY(lead, trail) \
+  (((::base_icu::UChar32)(lead) << 10UL) +   \
+   (::base_icu::UChar32)(trail)-CBU16_SURROGATE_OFFSET)
+
+/**
+ * Get the lead surrogate (0xd800..0xdbff) for a
+ * supplementary code point (0x10000..0x10ffff).
+ * @param supplementary 32-bit code point (U+10000..U+10ffff)
+ * @return lead surrogate (U+d800..U+dbff) for supplementary
+ * @stable ICU 2.4
+ */
+#define CBU16_LEAD(supplementary) \
+  (::base_icu::UChar)(((supplementary) >> 10) + 0xd7c0)
+
+/**
+ * Get the trail surrogate (0xdc00..0xdfff) for a
+ * supplementary code point (0x10000..0x10ffff).
+ * @param supplementary 32-bit code point (U+10000..U+10ffff)
+ * @return trail surrogate (U+dc00..U+dfff) for supplementary
+ * @stable ICU 2.4
+ */
+#define CBU16_TRAIL(supplementary) \
+  (::base_icu::UChar)(((supplementary)&0x3ff) | 0xdc00)
+
+/**
+ * How many 16-bit code units are used to encode this Unicode code point? (1 or
+ * 2) The result is not defined if c is not a Unicode code point
+ * (U+0000..U+10ffff).
+ * @param c 32-bit code point
+ * @return 1 or 2
+ * @stable ICU 2.4
+ */
+#define CBU16_LENGTH(c) ((uint32_t)(c) <= 0xffff ? 1 : 2)
+
+/**
+ * The maximum number of 16-bit code units per Unicode code point
+ * (U+0000..U+10ffff).
+ * @return 2
+ * @stable ICU 2.4
+ */
+#define CBU16_MAX_LENGTH 2
+
+/**
+ * Get a code point from a string at a code point boundary offset,
+ * and advance the offset to the next code point boundary.
+ * (Post-incrementing forward iteration.)
+ * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
+ *
+ * The length can be negative for a NUL-terminated string.
+ *
+ * The offset may point to the lead surrogate unit
+ * for a supplementary code point, in which case the macro will read
+ * the following trail surrogate as well.
+ * If the offset points to a trail surrogate or
+ * to a single, unpaired lead surrogate, then c is set to that unpaired
+ * surrogate.
+ *
+ * @param s const UChar * string
+ * @param i string offset, must be i<length
+ * @param length string length
+ * @param c output UChar32 variable
+ * @see U16_NEXT_UNSAFE
+ * @stable ICU 2.4
+ */
+#define CBU16_NEXT(s, i, length, c)                             \
+  {                                                             \
+    (c) = (s)[(i)++];                                           \
+    if (CBU16_IS_LEAD(c)) {                                     \
+      uint16_t __c2;                                            \
+      if ((i) != (length) && CBU16_IS_TRAIL(__c2 = (s)[(i)])) { \
+        ++(i);                                                  \
+        (c) = CBU16_GET_SUPPLEMENTARY((c), __c2);               \
+      }                                                         \
+    }                                                           \
+  }
+
+/**
+ * Append a code point to a string, overwriting 1 or 2 code units.
+ * The offset points to the current end of the string contents
+ * and is advanced (post-increment).
+ * "Unsafe" macro, assumes a valid code point and sufficient space in the
+ * string. Otherwise, the result is undefined.
+ *
+ * @param s const UChar * string buffer
+ * @param i string offset
+ * @param c code point to append
+ * @see U16_APPEND
+ * @stable ICU 2.4
+ */
+#define CBU16_APPEND_UNSAFE(s, i, c)                 \
+  {                                                  \
+    if ((uint32_t)(c) <= 0xffff) {                   \
+      (s)[(i)++] = (uint16_t)(c);                    \
+    } else {                                         \
+      (s)[(i)++] = (uint16_t)(((c) >> 10) + 0xd7c0); \
+      (s)[(i)++] = (uint16_t)(((c)&0x3ff) | 0xdc00); \
+    }                                                \
+  }
+
+}  // namespace base_icu
+
+#endif  // BASE_THIRD_PARTY_ICU_ICU_UTF_H_
diff --git a/src/base/timer/elapsed_timer.cc b/src/base/timer/elapsed_timer.cc
new file mode 100644 (file)
index 0000000..68992f4
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/timer/elapsed_timer.h"
+
+namespace base {
+
+ElapsedTimer::ElapsedTimer() {
+  begin_ = TicksNow();
+}
+
+ElapsedTimer::ElapsedTimer(ElapsedTimer&& other) {
+  begin_ = other.begin_;
+}
+
+void ElapsedTimer::operator=(ElapsedTimer&& other) {
+  begin_ = other.begin_;
+}
+
+TickDelta ElapsedTimer::Elapsed() const {
+  return TicksDelta(TicksNow(), begin_);
+}
+
+}  // namespace base
diff --git a/src/base/timer/elapsed_timer.h b/src/base/timer/elapsed_timer.h
new file mode 100644 (file)
index 0000000..fbbc83d
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TIMER_ELAPSED_TIMER_H_
+#define BASE_TIMER_ELAPSED_TIMER_H_
+
+#include "base/macros.h"
+#include "util/ticks.h"
+
+namespace base {
+
+// A simple wrapper around TicksNow().
+class ElapsedTimer {
+ public:
+  ElapsedTimer();
+  ElapsedTimer(ElapsedTimer&& other);
+
+  void operator=(ElapsedTimer&& other);
+
+  // Returns the time elapsed since object construction.
+  TickDelta Elapsed() const;
+
+ private:
+  Ticks begin_;
+
+  DISALLOW_COPY_AND_ASSIGN(ElapsedTimer);
+};
+
+}  // namespace base
+
+#endif  // BASE_TIMER_ELAPSED_TIMER_H_
diff --git a/src/base/value_iterators.cc b/src/base/value_iterators.cc
new file mode 100644 (file)
index 0000000..ba9c730
--- /dev/null
@@ -0,0 +1,228 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/value_iterators.h"
+
+namespace base {
+
+namespace detail {
+
+// ----------------------------------------------------------------------------
+// dict_iterator.
+
+dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {}
+
+dict_iterator::pointer::pointer(const pointer& ptr) = default;
+
+dict_iterator::dict_iterator(DictStorage::iterator dict_iter)
+    : dict_iter_(dict_iter) {}
+
+dict_iterator::dict_iterator(const dict_iterator& dict_iter) = default;
+
+dict_iterator& dict_iterator::operator=(const dict_iterator& dict_iter) =
+    default;
+
+dict_iterator::~dict_iterator() = default;
+
+dict_iterator::reference dict_iterator::operator*() {
+  return {dict_iter_->first, *dict_iter_->second};
+}
+
+dict_iterator::pointer dict_iterator::operator->() {
+  return pointer(operator*());
+}
+
+dict_iterator& dict_iterator::operator++() {
+  ++dict_iter_;
+  return *this;
+}
+
+dict_iterator dict_iterator::operator++(int) {
+  dict_iterator tmp(*this);
+  ++dict_iter_;
+  return tmp;
+}
+
+dict_iterator& dict_iterator::operator--() {
+  --dict_iter_;
+  return *this;
+}
+
+dict_iterator dict_iterator::operator--(int) {
+  dict_iterator tmp(*this);
+  --dict_iter_;
+  return tmp;
+}
+
+bool operator==(const dict_iterator& lhs, const dict_iterator& rhs) {
+  return lhs.dict_iter_ == rhs.dict_iter_;
+}
+
+bool operator!=(const dict_iterator& lhs, const dict_iterator& rhs) {
+  return !(lhs == rhs);
+}
+
+// ----------------------------------------------------------------------------
+// const_dict_iterator.
+
+const_dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {}
+
+const_dict_iterator::pointer::pointer(const pointer& ptr) = default;
+
+const_dict_iterator::const_dict_iterator(DictStorage::const_iterator dict_iter)
+    : dict_iter_(dict_iter) {}
+
+const_dict_iterator::const_dict_iterator(const const_dict_iterator& dict_iter) =
+    default;
+
+const_dict_iterator& const_dict_iterator::operator=(
+    const const_dict_iterator& dict_iter) = default;
+
+const_dict_iterator::~const_dict_iterator() = default;
+
+const_dict_iterator::reference const_dict_iterator::operator*() const {
+  return {dict_iter_->first, *dict_iter_->second};
+}
+
+const_dict_iterator::pointer const_dict_iterator::operator->() const {
+  return pointer(operator*());
+}
+
+const_dict_iterator& const_dict_iterator::operator++() {
+  ++dict_iter_;
+  return *this;
+}
+
+const_dict_iterator const_dict_iterator::operator++(int) {
+  const_dict_iterator tmp(*this);
+  ++dict_iter_;
+  return tmp;
+}
+
+const_dict_iterator& const_dict_iterator::operator--() {
+  --dict_iter_;
+  return *this;
+}
+
+const_dict_iterator const_dict_iterator::operator--(int) {
+  const_dict_iterator tmp(*this);
+  --dict_iter_;
+  return tmp;
+}
+
+bool operator==(const const_dict_iterator& lhs,
+                const const_dict_iterator& rhs) {
+  return lhs.dict_iter_ == rhs.dict_iter_;
+}
+
+bool operator!=(const const_dict_iterator& lhs,
+                const const_dict_iterator& rhs) {
+  return !(lhs == rhs);
+}
+
+// ----------------------------------------------------------------------------
+// dict_iterator_proxy.
+
+dict_iterator_proxy::dict_iterator_proxy(DictStorage* storage)
+    : storage_(storage) {}
+
+dict_iterator_proxy::iterator dict_iterator_proxy::begin() {
+  return iterator(storage_->begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::begin() const {
+  return const_iterator(storage_->begin());
+}
+
+dict_iterator_proxy::iterator dict_iterator_proxy::end() {
+  return iterator(storage_->end());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::end() const {
+  return const_iterator(storage_->end());
+}
+
+dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rbegin() {
+  return reverse_iterator(end());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rbegin()
+    const {
+  return const_reverse_iterator(end());
+}
+
+dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rend() {
+  return reverse_iterator(begin());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rend() const {
+  return const_reverse_iterator(begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::cbegin() const {
+  return const_iterator(begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::cend() const {
+  return const_iterator(end());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crbegin()
+    const {
+  return const_reverse_iterator(rbegin());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crend() const {
+  return const_reverse_iterator(rend());
+}
+
+// ----------------------------------------------------------------------------
+// const_dict_iterator_proxy.
+
+const_dict_iterator_proxy::const_dict_iterator_proxy(const DictStorage* storage)
+    : storage_(storage) {}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::begin()
+    const {
+  return const_iterator(storage_->begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::end()
+    const {
+  return const_iterator(storage_->end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::rbegin() const {
+  return const_reverse_iterator(end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::rend() const {
+  return const_reverse_iterator(begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cbegin()
+    const {
+  return const_iterator(begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cend()
+    const {
+  return const_iterator(end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::crbegin() const {
+  return const_reverse_iterator(rbegin());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::crend() const {
+  return const_reverse_iterator(rend());
+}
+
+}  // namespace detail
+
+}  // namespace base
diff --git a/src/base/value_iterators.h b/src/base/value_iterators.h
new file mode 100644 (file)
index 0000000..4c814c5
--- /dev/null
@@ -0,0 +1,191 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_VALUE_ITERATORS_H_
+#define BASE_VALUE_ITERATORS_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+
+namespace base {
+
+class Value;
+
+namespace detail {
+
+using DictStorage = base::flat_map<std::string, std::unique_ptr<Value>>;
+
+// This iterator closely resembles DictStorage::iterator, with one
+// important exception. It abstracts the underlying unique_ptr away, meaning its
+// value_type is std::pair<const std::string, Value>. It's reference type is a
+// std::pair<const std::string&, Value&>, so that callers have read-write
+// access without incurring a copy.
+class dict_iterator {
+ public:
+  using difference_type = DictStorage::iterator::difference_type;
+  using value_type = std::pair<const std::string, Value>;
+  using reference = std::pair<const std::string&, Value&>;
+  using iterator_category = std::bidirectional_iterator_tag;
+
+  class pointer {
+   public:
+    explicit pointer(const reference& ref);
+    pointer(const pointer& ptr);
+    pointer& operator=(const pointer& ptr) = delete;
+
+    reference* operator->() { return &ref_; }
+
+   private:
+    reference ref_;
+  };
+
+  explicit dict_iterator(DictStorage::iterator dict_iter);
+  dict_iterator(const dict_iterator& dict_iter);
+  dict_iterator& operator=(const dict_iterator& dict_iter);
+  ~dict_iterator();
+
+  reference operator*();
+  pointer operator->();
+
+  dict_iterator& operator++();
+  dict_iterator operator++(int);
+  dict_iterator& operator--();
+  dict_iterator operator--(int);
+
+  friend bool operator==(const dict_iterator& lhs, const dict_iterator& rhs);
+  friend bool operator!=(const dict_iterator& lhs, const dict_iterator& rhs);
+
+ private:
+  DictStorage::iterator dict_iter_;
+};
+
+// This iterator closely resembles DictStorage::const_iterator, with one
+// important exception. It abstracts the underlying unique_ptr away, meaning its
+// value_type is std::pair<const std::string, Value>. It's reference type is a
+// std::pair<const std::string&, const Value&>, so that callers have read-only
+// access without incurring a copy.
+class const_dict_iterator {
+ public:
+  using difference_type = DictStorage::const_iterator::difference_type;
+  using value_type = std::pair<const std::string, Value>;
+  using reference = std::pair<const std::string&, const Value&>;
+  using iterator_category = std::bidirectional_iterator_tag;
+
+  class pointer {
+   public:
+    explicit pointer(const reference& ref);
+    pointer(const pointer& ptr);
+    pointer& operator=(const pointer& ptr) = delete;
+
+    const reference* operator->() const { return &ref_; }
+
+   private:
+    const reference ref_;
+  };
+
+  explicit const_dict_iterator(DictStorage::const_iterator dict_iter);
+  const_dict_iterator(const const_dict_iterator& dict_iter);
+  const_dict_iterator& operator=(const const_dict_iterator& dict_iter);
+  ~const_dict_iterator();
+
+  reference operator*() const;
+  pointer operator->() const;
+
+  const_dict_iterator& operator++();
+  const_dict_iterator operator++(int);
+  const_dict_iterator& operator--();
+  const_dict_iterator operator--(int);
+
+  friend bool operator==(const const_dict_iterator& lhs,
+                         const const_dict_iterator& rhs);
+  friend bool operator!=(const const_dict_iterator& lhs,
+                         const const_dict_iterator& rhs);
+
+ private:
+  DictStorage::const_iterator dict_iter_;
+};
+
+// This class wraps the various |begin| and |end| methods of the underlying
+// DictStorage in dict_iterators and const_dict_iterators. This allows callers
+// to use this class for easy iteration over the underlying values, granting
+// them either read-only or read-write access, depending on the
+// const-qualification.
+class dict_iterator_proxy {
+ public:
+  using key_type = DictStorage::key_type;
+  using mapped_type = DictStorage::mapped_type::element_type;
+  using value_type = std::pair<key_type, mapped_type>;
+  using key_compare = DictStorage::key_compare;
+  using size_type = DictStorage::size_type;
+  using difference_type = DictStorage::difference_type;
+
+  using iterator = dict_iterator;
+  using const_iterator = const_dict_iterator;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  explicit dict_iterator_proxy(DictStorage* storage);
+
+  iterator begin();
+  const_iterator begin() const;
+  iterator end();
+  const_iterator end() const;
+
+  reverse_iterator rbegin();
+  const_reverse_iterator rbegin() const;
+  reverse_iterator rend();
+  const_reverse_iterator rend() const;
+
+  const_dict_iterator cbegin() const;
+  const_dict_iterator cend() const;
+  const_reverse_iterator crbegin() const;
+  const_reverse_iterator crend() const;
+
+ private:
+  DictStorage* storage_;
+};
+
+// This class wraps the various const |begin| and |end| methods of the
+// underlying DictStorage in const_dict_iterators. This allows callers to use
+// this class for easy iteration over the underlying values, granting them
+// either read-only access.
+class const_dict_iterator_proxy {
+ public:
+  using key_type = const DictStorage::key_type;
+  using mapped_type = const DictStorage::mapped_type::element_type;
+  using value_type = std::pair<key_type, mapped_type>;
+  using key_compare = DictStorage::key_compare;
+  using size_type = DictStorage::size_type;
+  using difference_type = DictStorage::difference_type;
+
+  using iterator = const_dict_iterator;
+  using const_iterator = const_dict_iterator;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  explicit const_dict_iterator_proxy(const DictStorage* storage);
+
+  const_iterator begin() const;
+  const_iterator end() const;
+
+  const_reverse_iterator rbegin() const;
+  const_reverse_iterator rend() const;
+
+  const_iterator cbegin() const;
+  const_iterator cend() const;
+  const_reverse_iterator crbegin() const;
+  const_reverse_iterator crend() const;
+
+ private:
+  const DictStorage* storage_;
+};
+}  // namespace detail
+
+}  // namespace base
+
+#endif  // BASE_VALUE_ITERATORS_H_
diff --git a/src/base/values.cc b/src/base/values.cc
new file mode 100644 (file)
index 0000000..25b10e3
--- /dev/null
@@ -0,0 +1,1309 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/values.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <cmath>
+#include <iterator>
+#include <new>
+#include <ostream>
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace base {
+
+namespace {
+
+const char* const kTypeNames[] = {"null",   "boolean",    "integer", "string",
+                                  "binary", "dictionary", "list"};
+static_assert(std::size(kTypeNames) ==
+                  static_cast<size_t>(Value::Type::LIST) + 1,
+              "kTypeNames Has Wrong Size");
+
+std::unique_ptr<Value> CopyWithoutEmptyChildren(const Value& node);
+
+// Make a deep copy of |node|, but don't include empty lists or dictionaries
+// in the copy. It's possible for this function to return NULL and it
+// expects |node| to always be non-NULL.
+std::unique_ptr<Value> CopyListWithoutEmptyChildren(const Value& list) {
+  Value copy(Value::Type::LIST);
+  for (const auto& entry : list.GetList()) {
+    std::unique_ptr<Value> child_copy = CopyWithoutEmptyChildren(entry);
+    if (child_copy)
+      copy.GetList().push_back(std::move(*child_copy));
+  }
+  return copy.GetList().empty() ? nullptr
+                                : std::make_unique<Value>(std::move(copy));
+}
+
+std::unique_ptr<DictionaryValue> CopyDictionaryWithoutEmptyChildren(
+    const DictionaryValue& dict) {
+  std::unique_ptr<DictionaryValue> copy;
+  for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+    std::unique_ptr<Value> child_copy = CopyWithoutEmptyChildren(it.value());
+    if (child_copy) {
+      if (!copy)
+        copy = std::make_unique<DictionaryValue>();
+      copy->SetWithoutPathExpansion(it.key(), std::move(child_copy));
+    }
+  }
+  return copy;
+}
+
+std::unique_ptr<Value> CopyWithoutEmptyChildren(const Value& node) {
+  switch (node.type()) {
+    case Value::Type::LIST:
+      return CopyListWithoutEmptyChildren(static_cast<const ListValue&>(node));
+
+    case Value::Type::DICTIONARY:
+      return CopyDictionaryWithoutEmptyChildren(
+          static_cast<const DictionaryValue&>(node));
+
+    default:
+      return std::make_unique<Value>(node.Clone());
+  }
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<Value> Value::CreateWithCopiedBuffer(const char* buffer,
+                                                     size_t size) {
+  return std::make_unique<Value>(BlobStorage(buffer, buffer + size));
+}
+
+// static
+Value Value::FromUniquePtrValue(std::unique_ptr<Value> val) {
+  return std::move(*val);
+}
+
+// static
+std::unique_ptr<Value> Value::ToUniquePtrValue(Value val) {
+  return std::make_unique<Value>(std::move(val));
+}
+
+Value::Value(Value&& that) noexcept {
+  InternalMoveConstructFrom(std::move(that));
+}
+
+Value::Value() noexcept : type_(Type::NONE) {}
+
+Value::Value(Type type) : type_(type) {
+  // Initialize with the default value.
+  switch (type_) {
+    case Type::NONE:
+      return;
+
+    case Type::BOOLEAN:
+      bool_value_ = false;
+      return;
+    case Type::INTEGER:
+      int_value_ = 0;
+      return;
+    case Type::STRING:
+      new (&string_value_) std::string();
+      return;
+    case Type::BINARY:
+      new (&binary_value_) BlobStorage();
+      return;
+    case Type::DICTIONARY:
+      new (&dict_) DictStorage();
+      return;
+    case Type::LIST:
+      new (&list_) ListStorage();
+      return;
+  }
+}
+
+Value::Value(bool in_bool) : type_(Type::BOOLEAN), bool_value_(in_bool) {}
+
+Value::Value(int in_int) : type_(Type::INTEGER), int_value_(in_int) {}
+
+Value::Value(const char* in_string) : Value(std::string(in_string)) {}
+
+Value::Value(std::string_view in_string) : Value(std::string(in_string)) {}
+
+Value::Value(std::string&& in_string) noexcept
+    : type_(Type::STRING), string_value_(std::move(in_string)) {
+  DCHECK(IsStringUTF8(string_value_));
+}
+
+Value::Value(const char16_t* in_string16)
+    : Value(std::u16string_view(in_string16)) {}
+
+Value::Value(std::u16string_view in_string16)
+    : Value(UTF16ToUTF8(in_string16)) {}
+
+Value::Value(const BlobStorage& in_blob)
+    : type_(Type::BINARY), binary_value_(in_blob) {}
+
+Value::Value(BlobStorage&& in_blob) noexcept
+    : type_(Type::BINARY), binary_value_(std::move(in_blob)) {}
+
+Value::Value(const DictStorage& in_dict) : type_(Type::DICTIONARY), dict_() {
+  dict_.reserve(in_dict.size());
+  for (const auto& it : in_dict) {
+    dict_.try_emplace(dict_.end(), it.first,
+                      std::make_unique<Value>(it.second->Clone()));
+  }
+}
+
+Value::Value(DictStorage&& in_dict) noexcept
+    : type_(Type::DICTIONARY), dict_(std::move(in_dict)) {}
+
+Value::Value(const ListStorage& in_list) : type_(Type::LIST), list_() {
+  list_.reserve(in_list.size());
+  for (const auto& val : in_list)
+    list_.emplace_back(val.Clone());
+}
+
+Value::Value(ListStorage&& in_list) noexcept
+    : type_(Type::LIST), list_(std::move(in_list)) {}
+
+Value& Value::operator=(Value&& that) noexcept {
+  InternalCleanup();
+  InternalMoveConstructFrom(std::move(that));
+
+  return *this;
+}
+
+Value Value::Clone() const {
+  switch (type_) {
+    case Type::NONE:
+      return Value();
+    case Type::BOOLEAN:
+      return Value(bool_value_);
+    case Type::INTEGER:
+      return Value(int_value_);
+    case Type::STRING:
+      return Value(string_value_);
+    case Type::BINARY:
+      return Value(binary_value_);
+    case Type::DICTIONARY:
+      return Value(dict_);
+    case Type::LIST:
+      return Value(list_);
+  }
+
+  NOTREACHED();
+  return Value();
+}
+
+Value::~Value() {
+  InternalCleanup();
+}
+
+// static
+const char* Value::GetTypeName(Value::Type type) {
+  DCHECK_GE(static_cast<int>(type), 0);
+  DCHECK_LT(static_cast<size_t>(type), std::size(kTypeNames));
+  return kTypeNames[static_cast<size_t>(type)];
+}
+
+bool Value::GetBool() const {
+  CHECK(is_bool());
+  return bool_value_;
+}
+
+int Value::GetInt() const {
+  CHECK(is_int());
+  return int_value_;
+}
+
+const std::string& Value::GetString() const {
+  CHECK(is_string());
+  return string_value_;
+}
+
+const Value::BlobStorage& Value::GetBlob() const {
+  CHECK(is_blob());
+  return binary_value_;
+}
+
+Value::ListStorage& Value::GetList() {
+  CHECK(is_list());
+  return list_;
+}
+
+const Value::ListStorage& Value::GetList() const {
+  CHECK(is_list());
+  return list_;
+}
+
+Value* Value::FindKey(std::string_view key) {
+  return const_cast<Value*>(static_cast<const Value*>(this)->FindKey(key));
+}
+
+const Value* Value::FindKey(std::string_view key) const {
+  CHECK(is_dict());
+  auto found = dict_.find(key);
+  if (found == dict_.end())
+    return nullptr;
+  return found->second.get();
+}
+
+Value* Value::FindKeyOfType(std::string_view key, Type type) {
+  return const_cast<Value*>(
+      static_cast<const Value*>(this)->FindKeyOfType(key, type));
+}
+
+const Value* Value::FindKeyOfType(std::string_view key, Type type) const {
+  const Value* result = FindKey(key);
+  if (!result || result->type() != type)
+    return nullptr;
+  return result;
+}
+
+bool Value::RemoveKey(std::string_view key) {
+  CHECK(is_dict());
+  // NOTE: Can't directly return dict_->erase(key) due to MSVC warning C4800.
+  return dict_.erase(key) != 0;
+}
+
+Value* Value::SetKey(std::string_view key, Value value) {
+  CHECK(is_dict());
+  // NOTE: We can't use |insert_or_assign| here, as only |try_emplace| does
+  // an explicit conversion from std::string_view to std::string if necessary.
+  auto val_ptr = std::make_unique<Value>(std::move(value));
+  auto result = dict_.try_emplace(key, std::move(val_ptr));
+  if (!result.second) {
+    // val_ptr is guaranteed to be still intact at this point.
+    result.first->second = std::move(val_ptr);
+  }
+  return result.first->second.get();
+}
+
+Value* Value::SetKey(std::string&& key, Value value) {
+  CHECK(is_dict());
+  return dict_
+      .insert_or_assign(std::move(key),
+                        std::make_unique<Value>(std::move(value)))
+      .first->second.get();
+}
+
+Value* Value::SetKey(const char* key, Value value) {
+  return SetKey(std::string_view(key), std::move(value));
+}
+
+Value* Value::FindPath(std::initializer_list<std::string_view> path) {
+  return const_cast<Value*>(const_cast<const Value*>(this)->FindPath(path));
+}
+
+Value* Value::FindPath(span<const std::string_view> path) {
+  return const_cast<Value*>(const_cast<const Value*>(this)->FindPath(path));
+}
+
+const Value* Value::FindPath(
+    std::initializer_list<std::string_view> path) const {
+  DCHECK_GE(path.size(), 2u) << "Use FindKey() for a path of length 1.";
+  return FindPath(make_span(path.begin(), path.size()));
+}
+
+const Value* Value::FindPath(span<const std::string_view> path) const {
+  const Value* cur = this;
+  for (const std::string_view& component : path) {
+    if (!cur->is_dict() || (cur = cur->FindKey(component)) == nullptr)
+      return nullptr;
+  }
+  return cur;
+}
+
+Value* Value::FindPathOfType(std::initializer_list<std::string_view> path,
+                             Type type) {
+  return const_cast<Value*>(
+      const_cast<const Value*>(this)->FindPathOfType(path, type));
+}
+
+Value* Value::FindPathOfType(span<const std::string_view> path, Type type) {
+  return const_cast<Value*>(
+      const_cast<const Value*>(this)->FindPathOfType(path, type));
+}
+
+const Value* Value::FindPathOfType(std::initializer_list<std::string_view> path,
+                                   Type type) const {
+  DCHECK_GE(path.size(), 2u) << "Use FindKeyOfType() for a path of length 1.";
+  return FindPathOfType(make_span(path.begin(), path.size()), type);
+}
+
+const Value* Value::FindPathOfType(span<const std::string_view> path,
+                                   Type type) const {
+  const Value* result = FindPath(path);
+  if (!result || result->type() != type)
+    return nullptr;
+  return result;
+}
+
+Value* Value::SetPath(std::initializer_list<std::string_view> path,
+                      Value value) {
+  DCHECK_GE(path.size(), 2u) << "Use SetKey() for a path of length 1.";
+  return SetPath(make_span(path.begin(), path.size()), std::move(value));
+}
+
+Value* Value::SetPath(span<const std::string_view> path, Value value) {
+  DCHECK_NE(path.begin(), path.end());  // Can't be empty path.
+
+  // Walk/construct intermediate dictionaries. The last element requires
+  // special handling so skip it in this loop.
+  Value* cur = this;
+  const std::string_view* cur_path = path.begin();
+  for (; (cur_path + 1) < path.end(); ++cur_path) {
+    if (!cur->is_dict())
+      return nullptr;
+
+    // Use lower_bound to avoid doing the search twice for missing keys.
+    const std::string_view path_component = *cur_path;
+    auto found = cur->dict_.lower_bound(path_component);
+    if (found == cur->dict_.end() || found->first != path_component) {
+      // No key found, insert one.
+      auto inserted = cur->dict_.try_emplace(
+          found, path_component, std::make_unique<Value>(Type::DICTIONARY));
+      cur = inserted->second.get();
+    } else {
+      cur = found->second.get();
+    }
+  }
+
+  // "cur" will now contain the last dictionary to insert or replace into.
+  if (!cur->is_dict())
+    return nullptr;
+  return cur->SetKey(*cur_path, std::move(value));
+}
+
+bool Value::RemovePath(std::initializer_list<std::string_view> path) {
+  DCHECK_GE(path.size(), 2u) << "Use RemoveKey() for a path of length 1.";
+  return RemovePath(make_span(path.begin(), path.size()));
+}
+
+bool Value::RemovePath(span<const std::string_view> path) {
+  if (!is_dict() || path.empty())
+    return false;
+
+  if (path.size() == 1)
+    return RemoveKey(path[0]);
+
+  auto found = dict_.find(path[0]);
+  if (found == dict_.end() || !found->second->is_dict())
+    return false;
+
+  bool removed = found->second->RemovePath(path.subspan(1));
+  if (removed && found->second->dict_.empty())
+    dict_.erase(found);
+
+  return removed;
+}
+
+Value::dict_iterator_proxy Value::DictItems() {
+  CHECK(is_dict());
+  return dict_iterator_proxy(&dict_);
+}
+
+Value::const_dict_iterator_proxy Value::DictItems() const {
+  CHECK(is_dict());
+  return const_dict_iterator_proxy(&dict_);
+}
+
+size_t Value::DictSize() const {
+  CHECK(is_dict());
+  return dict_.size();
+}
+
+bool Value::DictEmpty() const {
+  CHECK(is_dict());
+  return dict_.empty();
+}
+
+bool Value::GetAsBoolean(bool* out_value) const {
+  if (out_value && is_bool()) {
+    *out_value = bool_value_;
+    return true;
+  }
+  return is_bool();
+}
+
+bool Value::GetAsInteger(int* out_value) const {
+  if (out_value && is_int()) {
+    *out_value = int_value_;
+    return true;
+  }
+  return is_int();
+}
+
+bool Value::GetAsString(std::string* out_value) const {
+  if (out_value && is_string()) {
+    *out_value = string_value_;
+    return true;
+  }
+  return is_string();
+}
+
+bool Value::GetAsString(std::u16string* out_value) const {
+  if (out_value && is_string()) {
+    *out_value = UTF8ToUTF16(string_value_);
+    return true;
+  }
+  return is_string();
+}
+
+bool Value::GetAsString(const Value** out_value) const {
+  if (out_value && is_string()) {
+    *out_value = static_cast<const Value*>(this);
+    return true;
+  }
+  return is_string();
+}
+
+bool Value::GetAsString(std::string_view* out_value) const {
+  if (out_value && is_string()) {
+    *out_value = string_value_;
+    return true;
+  }
+  return is_string();
+}
+
+bool Value::GetAsList(ListValue** out_value) {
+  if (out_value && is_list()) {
+    *out_value = static_cast<ListValue*>(this);
+    return true;
+  }
+  return is_list();
+}
+
+bool Value::GetAsList(const ListValue** out_value) const {
+  if (out_value && is_list()) {
+    *out_value = static_cast<const ListValue*>(this);
+    return true;
+  }
+  return is_list();
+}
+
+bool Value::GetAsDictionary(DictionaryValue** out_value) {
+  if (out_value && is_dict()) {
+    *out_value = static_cast<DictionaryValue*>(this);
+    return true;
+  }
+  return is_dict();
+}
+
+bool Value::GetAsDictionary(const DictionaryValue** out_value) const {
+  if (out_value && is_dict()) {
+    *out_value = static_cast<const DictionaryValue*>(this);
+    return true;
+  }
+  return is_dict();
+}
+
+Value* Value::DeepCopy() const {
+  return new Value(Clone());
+}
+
+std::unique_ptr<Value> Value::CreateDeepCopy() const {
+  return std::make_unique<Value>(Clone());
+}
+
+bool operator==(const Value& lhs, const Value& rhs) {
+  if (lhs.type_ != rhs.type_)
+    return false;
+
+  switch (lhs.type_) {
+    case Value::Type::NONE:
+      return true;
+    case Value::Type::BOOLEAN:
+      return lhs.bool_value_ == rhs.bool_value_;
+    case Value::Type::INTEGER:
+      return lhs.int_value_ == rhs.int_value_;
+    case Value::Type::STRING:
+      return lhs.string_value_ == rhs.string_value_;
+    case Value::Type::BINARY:
+      return lhs.binary_value_ == rhs.binary_value_;
+    // TODO(crbug.com/646113): Clean this up when DictionaryValue and ListValue
+    // are completely inlined.
+    case Value::Type::DICTIONARY:
+      if (lhs.dict_.size() != rhs.dict_.size())
+        return false;
+      return std::equal(
+          std::begin(lhs.dict_), std::end(lhs.dict_), std::begin(rhs.dict_),
+          [](const auto& u, const auto& v) {
+            return std::tie(u.first, *u.second) == std::tie(v.first, *v.second);
+          });
+    case Value::Type::LIST:
+      return lhs.list_ == rhs.list_;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
+bool operator!=(const Value& lhs, const Value& rhs) {
+  return !(lhs == rhs);
+}
+
+bool operator<(const Value& lhs, const Value& rhs) {
+  if (lhs.type_ != rhs.type_)
+    return lhs.type_ < rhs.type_;
+
+  switch (lhs.type_) {
+    case Value::Type::NONE:
+      return false;
+    case Value::Type::BOOLEAN:
+      return lhs.bool_value_ < rhs.bool_value_;
+    case Value::Type::INTEGER:
+      return lhs.int_value_ < rhs.int_value_;
+    case Value::Type::STRING:
+      return lhs.string_value_ < rhs.string_value_;
+    case Value::Type::BINARY:
+      return lhs.binary_value_ < rhs.binary_value_;
+    // TODO(crbug.com/646113): Clean this up when DictionaryValue and ListValue
+    // are completely inlined.
+    case Value::Type::DICTIONARY:
+      return std::lexicographical_compare(
+          std::begin(lhs.dict_), std::end(lhs.dict_), std::begin(rhs.dict_),
+          std::end(rhs.dict_),
+          [](const Value::DictStorage::value_type& u,
+             const Value::DictStorage::value_type& v) {
+            return std::tie(u.first, *u.second) < std::tie(v.first, *v.second);
+          });
+    case Value::Type::LIST:
+      return lhs.list_ < rhs.list_;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
+bool operator>(const Value& lhs, const Value& rhs) {
+  return rhs < lhs;
+}
+
+bool operator<=(const Value& lhs, const Value& rhs) {
+  return !(rhs < lhs);
+}
+
+bool operator>=(const Value& lhs, const Value& rhs) {
+  return !(lhs < rhs);
+}
+
+bool Value::Equals(const Value* other) const {
+  DCHECK(other);
+  return *this == *other;
+}
+
+void Value::InternalMoveConstructFrom(Value&& that) {
+  type_ = that.type_;
+
+  switch (type_) {
+    case Type::NONE:
+      return;
+    case Type::BOOLEAN:
+      bool_value_ = that.bool_value_;
+      return;
+    case Type::INTEGER:
+      int_value_ = that.int_value_;
+      return;
+    case Type::STRING:
+      new (&string_value_) std::string(std::move(that.string_value_));
+      return;
+    case Type::BINARY:
+      new (&binary_value_) BlobStorage(std::move(that.binary_value_));
+      return;
+    case Type::DICTIONARY:
+      new (&dict_) DictStorage(std::move(that.dict_));
+      return;
+    case Type::LIST:
+      new (&list_) ListStorage(std::move(that.list_));
+      return;
+  }
+}
+
+void Value::InternalCleanup() {
+  switch (type_) {
+    case Type::NONE:
+    case Type::BOOLEAN:
+    case Type::INTEGER:
+      // Nothing to do
+      return;
+
+    case Type::STRING:
+      string_value_.~basic_string();
+      return;
+    case Type::BINARY:
+      binary_value_.~BlobStorage();
+      return;
+    case Type::DICTIONARY:
+      dict_.~DictStorage();
+      return;
+    case Type::LIST:
+      list_.~ListStorage();
+      return;
+  }
+}
+
+///////////////////// DictionaryValue ////////////////////
+
+// static
+std::unique_ptr<DictionaryValue> DictionaryValue::From(
+    std::unique_ptr<Value> value) {
+  DictionaryValue* out;
+  if (value && value->GetAsDictionary(&out)) {
+    ignore_result(value.release());
+    return WrapUnique(out);
+  }
+  return nullptr;
+}
+
+DictionaryValue::DictionaryValue() : Value(Type::DICTIONARY) {}
+DictionaryValue::DictionaryValue(const DictStorage& in_dict) : Value(in_dict) {}
+DictionaryValue::DictionaryValue(DictStorage&& in_dict) noexcept
+    : Value(std::move(in_dict)) {}
+
+bool DictionaryValue::HasKey(std::string_view key) const {
+  DCHECK(IsStringUTF8(key));
+  auto current_entry = dict_.find(key);
+  DCHECK((current_entry == dict_.end()) || current_entry->second);
+  return current_entry != dict_.end();
+}
+
+void DictionaryValue::Clear() {
+  dict_.clear();
+}
+
+Value* DictionaryValue::Set(std::string_view path,
+                            std::unique_ptr<Value> in_value) {
+  DCHECK(IsStringUTF8(path));
+  DCHECK(in_value);
+
+  std::string_view current_path(path);
+  Value* current_dictionary = this;
+  for (size_t delimiter_position = current_path.find('.');
+       delimiter_position != std::string_view::npos;
+       delimiter_position = current_path.find('.')) {
+    // Assume that we're indexing into a dictionary.
+    std::string_view key = current_path.substr(0, delimiter_position);
+    Value* child_dictionary =
+        current_dictionary->FindKeyOfType(key, Type::DICTIONARY);
+    if (!child_dictionary) {
+      child_dictionary =
+          current_dictionary->SetKey(key, Value(Type::DICTIONARY));
+    }
+
+    current_dictionary = child_dictionary;
+    current_path = current_path.substr(delimiter_position + 1);
+  }
+
+  return static_cast<DictionaryValue*>(current_dictionary)
+      ->SetWithoutPathExpansion(current_path, std::move(in_value));
+}
+
+Value* DictionaryValue::SetBoolean(std::string_view path, bool in_value) {
+  return Set(path, std::make_unique<Value>(in_value));
+}
+
+Value* DictionaryValue::SetInteger(std::string_view path, int in_value) {
+  return Set(path, std::make_unique<Value>(in_value));
+}
+
+Value* DictionaryValue::SetString(std::string_view path,
+                                  std::string_view in_value) {
+  return Set(path, std::make_unique<Value>(in_value));
+}
+
+Value* DictionaryValue::SetString(std::string_view path,
+                                  const std::u16string& in_value) {
+  return Set(path, std::make_unique<Value>(in_value));
+}
+
+DictionaryValue* DictionaryValue::SetDictionary(
+    std::string_view path,
+    std::unique_ptr<DictionaryValue> in_value) {
+  return static_cast<DictionaryValue*>(Set(path, std::move(in_value)));
+}
+
+ListValue* DictionaryValue::SetList(std::string_view path,
+                                    std::unique_ptr<ListValue> in_value) {
+  return static_cast<ListValue*>(Set(path, std::move(in_value)));
+}
+
+Value* DictionaryValue::SetWithoutPathExpansion(
+    std::string_view key,
+    std::unique_ptr<Value> in_value) {
+  // NOTE: We can't use |insert_or_assign| here, as only |try_emplace| does
+  // an explicit conversion from std::string_view to std::string if necessary.
+  auto result = dict_.try_emplace(key, std::move(in_value));
+  if (!result.second) {
+    // in_value is guaranteed to be still intact at this point.
+    result.first->second = std::move(in_value);
+  }
+  return result.first->second.get();
+}
+
+bool DictionaryValue::Get(std::string_view path,
+                          const Value** out_value) const {
+  DCHECK(IsStringUTF8(path));
+  std::string_view current_path(path);
+  const DictionaryValue* current_dictionary = this;
+  for (size_t delimiter_position = current_path.find('.');
+       delimiter_position != std::string::npos;
+       delimiter_position = current_path.find('.')) {
+    const DictionaryValue* child_dictionary = nullptr;
+    if (!current_dictionary->GetDictionaryWithoutPathExpansion(
+            current_path.substr(0, delimiter_position), &child_dictionary)) {
+      return false;
+    }
+
+    current_dictionary = child_dictionary;
+    current_path = current_path.substr(delimiter_position + 1);
+  }
+
+  return current_dictionary->GetWithoutPathExpansion(current_path, out_value);
+}
+
+bool DictionaryValue::Get(std::string_view path, Value** out_value) {
+  return static_cast<const DictionaryValue&>(*this).Get(
+      path, const_cast<const Value**>(out_value));
+}
+
+bool DictionaryValue::GetBoolean(std::string_view path,
+                                 bool* bool_value) const {
+  const Value* value;
+  if (!Get(path, &value))
+    return false;
+
+  return value->GetAsBoolean(bool_value);
+}
+
+bool DictionaryValue::GetInteger(std::string_view path, int* out_value) const {
+  const Value* value;
+  if (!Get(path, &value))
+    return false;
+
+  return value->GetAsInteger(out_value);
+}
+
+bool DictionaryValue::GetString(std::string_view path,
+                                std::string* out_value) const {
+  const Value* value;
+  if (!Get(path, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool DictionaryValue::GetString(std::string_view path,
+                                std::u16string* out_value) const {
+  const Value* value;
+  if (!Get(path, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool DictionaryValue::GetStringASCII(std::string_view path,
+                                     std::string* out_value) const {
+  std::string out;
+  if (!GetString(path, &out))
+    return false;
+
+  if (!IsStringASCII(out)) {
+    NOTREACHED();
+    return false;
+  }
+
+  out_value->assign(out);
+  return true;
+}
+
+bool DictionaryValue::GetBinary(std::string_view path,
+                                const Value** out_value) const {
+  const Value* value;
+  bool result = Get(path, &value);
+  if (!result || !value->is_blob())
+    return false;
+
+  if (out_value)
+    *out_value = value;
+
+  return true;
+}
+
+bool DictionaryValue::GetBinary(std::string_view path, Value** out_value) {
+  return static_cast<const DictionaryValue&>(*this).GetBinary(
+      path, const_cast<const Value**>(out_value));
+}
+
+bool DictionaryValue::GetDictionary(std::string_view path,
+                                    const DictionaryValue** out_value) const {
+  const Value* value;
+  bool result = Get(path, &value);
+  if (!result || !value->is_dict())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const DictionaryValue*>(value);
+
+  return true;
+}
+
+bool DictionaryValue::GetDictionary(std::string_view path,
+                                    DictionaryValue** out_value) {
+  return static_cast<const DictionaryValue&>(*this).GetDictionary(
+      path, const_cast<const DictionaryValue**>(out_value));
+}
+
+bool DictionaryValue::GetList(std::string_view path,
+                              const ListValue** out_value) const {
+  const Value* value;
+  bool result = Get(path, &value);
+  if (!result || !value->is_list())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const ListValue*>(value);
+
+  return true;
+}
+
+bool DictionaryValue::GetList(std::string_view path, ListValue** out_value) {
+  return static_cast<const DictionaryValue&>(*this).GetList(
+      path, const_cast<const ListValue**>(out_value));
+}
+
+bool DictionaryValue::GetWithoutPathExpansion(std::string_view key,
+                                              const Value** out_value) const {
+  DCHECK(IsStringUTF8(key));
+  auto entry_iterator = dict_.find(key);
+  if (entry_iterator == dict_.end())
+    return false;
+
+  if (out_value)
+    *out_value = entry_iterator->second.get();
+  return true;
+}
+
+bool DictionaryValue::GetWithoutPathExpansion(std::string_view key,
+                                              Value** out_value) {
+  return static_cast<const DictionaryValue&>(*this).GetWithoutPathExpansion(
+      key, const_cast<const Value**>(out_value));
+}
+
+bool DictionaryValue::GetBooleanWithoutPathExpansion(std::string_view key,
+                                                     bool* out_value) const {
+  const Value* value;
+  if (!GetWithoutPathExpansion(key, &value))
+    return false;
+
+  return value->GetAsBoolean(out_value);
+}
+
+bool DictionaryValue::GetIntegerWithoutPathExpansion(std::string_view key,
+                                                     int* out_value) const {
+  const Value* value;
+  if (!GetWithoutPathExpansion(key, &value))
+    return false;
+
+  return value->GetAsInteger(out_value);
+}
+
+bool DictionaryValue::GetStringWithoutPathExpansion(
+    std::string_view key,
+    std::string* out_value) const {
+  const Value* value;
+  if (!GetWithoutPathExpansion(key, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool DictionaryValue::GetStringWithoutPathExpansion(
+    std::string_view key,
+    std::u16string* out_value) const {
+  const Value* value;
+  if (!GetWithoutPathExpansion(key, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool DictionaryValue::GetDictionaryWithoutPathExpansion(
+    std::string_view key,
+    const DictionaryValue** out_value) const {
+  const Value* value;
+  bool result = GetWithoutPathExpansion(key, &value);
+  if (!result || !value->is_dict())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const DictionaryValue*>(value);
+
+  return true;
+}
+
+bool DictionaryValue::GetDictionaryWithoutPathExpansion(
+    std::string_view key,
+    DictionaryValue** out_value) {
+  const DictionaryValue& const_this =
+      static_cast<const DictionaryValue&>(*this);
+  return const_this.GetDictionaryWithoutPathExpansion(
+      key, const_cast<const DictionaryValue**>(out_value));
+}
+
+bool DictionaryValue::GetListWithoutPathExpansion(
+    std::string_view key,
+    const ListValue** out_value) const {
+  const Value* value;
+  bool result = GetWithoutPathExpansion(key, &value);
+  if (!result || !value->is_list())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const ListValue*>(value);
+
+  return true;
+}
+
+bool DictionaryValue::GetListWithoutPathExpansion(std::string_view key,
+                                                  ListValue** out_value) {
+  return static_cast<const DictionaryValue&>(*this).GetListWithoutPathExpansion(
+      key, const_cast<const ListValue**>(out_value));
+}
+
+bool DictionaryValue::Remove(std::string_view path,
+                             std::unique_ptr<Value>* out_value) {
+  DCHECK(IsStringUTF8(path));
+  std::string_view current_path(path);
+  DictionaryValue* current_dictionary = this;
+  size_t delimiter_position = current_path.rfind('.');
+  if (delimiter_position != std::string_view::npos) {
+    if (!GetDictionary(current_path.substr(0, delimiter_position),
+                       &current_dictionary))
+      return false;
+    current_path = current_path.substr(delimiter_position + 1);
+  }
+
+  return current_dictionary->RemoveWithoutPathExpansion(current_path,
+                                                        out_value);
+}
+
+bool DictionaryValue::RemoveWithoutPathExpansion(
+    std::string_view key,
+    std::unique_ptr<Value>* out_value) {
+  DCHECK(IsStringUTF8(key));
+  auto entry_iterator = dict_.find(key);
+  if (entry_iterator == dict_.end())
+    return false;
+
+  if (out_value)
+    *out_value = std::move(entry_iterator->second);
+  dict_.erase(entry_iterator);
+  return true;
+}
+
+bool DictionaryValue::RemovePath(std::string_view path,
+                                 std::unique_ptr<Value>* out_value) {
+  bool result = false;
+  size_t delimiter_position = path.find('.');
+
+  if (delimiter_position == std::string::npos)
+    return RemoveWithoutPathExpansion(path, out_value);
+
+  std::string_view subdict_path = path.substr(0, delimiter_position);
+  DictionaryValue* subdict = nullptr;
+  if (!GetDictionary(subdict_path, &subdict))
+    return false;
+  result = subdict->RemovePath(path.substr(delimiter_position + 1), out_value);
+  if (result && subdict->empty())
+    RemoveWithoutPathExpansion(subdict_path, nullptr);
+
+  return result;
+}
+
+std::unique_ptr<DictionaryValue> DictionaryValue::DeepCopyWithoutEmptyChildren()
+    const {
+  std::unique_ptr<DictionaryValue> copy =
+      CopyDictionaryWithoutEmptyChildren(*this);
+  if (!copy)
+    copy = std::make_unique<DictionaryValue>();
+  return copy;
+}
+
+void DictionaryValue::MergeDictionary(const DictionaryValue* dictionary) {
+  CHECK(dictionary->is_dict());
+  for (DictionaryValue::Iterator it(*dictionary); !it.IsAtEnd(); it.Advance()) {
+    const Value* merge_value = &it.value();
+    // Check whether we have to merge dictionaries.
+    if (merge_value->is_dict()) {
+      DictionaryValue* sub_dict;
+      if (GetDictionaryWithoutPathExpansion(it.key(), &sub_dict)) {
+        sub_dict->MergeDictionary(
+            static_cast<const DictionaryValue*>(merge_value));
+        continue;
+      }
+    }
+    // All other cases: Make a copy and hook it up.
+    SetKey(it.key(), merge_value->Clone());
+  }
+}
+
+void DictionaryValue::Swap(DictionaryValue* other) {
+  CHECK(other->is_dict());
+  dict_.swap(other->dict_);
+}
+
+DictionaryValue::Iterator::Iterator(const DictionaryValue& target)
+    : target_(target), it_(target.dict_.begin()) {}
+
+DictionaryValue::Iterator::Iterator(const Iterator& other) = default;
+
+DictionaryValue::Iterator::~Iterator() = default;
+
+DictionaryValue* DictionaryValue::DeepCopy() const {
+  return new DictionaryValue(dict_);
+}
+
+std::unique_ptr<DictionaryValue> DictionaryValue::CreateDeepCopy() const {
+  return std::make_unique<DictionaryValue>(dict_);
+}
+
+///////////////////// ListValue ////////////////////
+
+// static
+std::unique_ptr<ListValue> ListValue::From(std::unique_ptr<Value> value) {
+  ListValue* out;
+  if (value && value->GetAsList(&out)) {
+    ignore_result(value.release());
+    return WrapUnique(out);
+  }
+  return nullptr;
+}
+
+ListValue::ListValue() : Value(Type::LIST) {}
+ListValue::ListValue(const ListStorage& in_list) : Value(in_list) {}
+ListValue::ListValue(ListStorage&& in_list) noexcept
+    : Value(std::move(in_list)) {}
+
+void ListValue::Clear() {
+  list_.clear();
+}
+
+void ListValue::Reserve(size_t n) {
+  list_.reserve(n);
+}
+
+bool ListValue::Set(size_t index, std::unique_ptr<Value> in_value) {
+  if (!in_value)
+    return false;
+
+  if (index >= list_.size())
+    list_.resize(index + 1);
+
+  list_[index] = std::move(*in_value);
+  return true;
+}
+
+bool ListValue::Get(size_t index, const Value** out_value) const {
+  if (index >= list_.size())
+    return false;
+
+  if (out_value)
+    *out_value = &list_[index];
+
+  return true;
+}
+
+bool ListValue::Get(size_t index, Value** out_value) {
+  return static_cast<const ListValue&>(*this).Get(
+      index, const_cast<const Value**>(out_value));
+}
+
+bool ListValue::GetBoolean(size_t index, bool* bool_value) const {
+  const Value* value;
+  if (!Get(index, &value))
+    return false;
+
+  return value->GetAsBoolean(bool_value);
+}
+
+bool ListValue::GetInteger(size_t index, int* out_value) const {
+  const Value* value;
+  if (!Get(index, &value))
+    return false;
+
+  return value->GetAsInteger(out_value);
+}
+
+bool ListValue::GetString(size_t index, std::string* out_value) const {
+  const Value* value;
+  if (!Get(index, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool ListValue::GetString(size_t index, std::u16string* out_value) const {
+  const Value* value;
+  if (!Get(index, &value))
+    return false;
+
+  return value->GetAsString(out_value);
+}
+
+bool ListValue::GetDictionary(size_t index,
+                              const DictionaryValue** out_value) const {
+  const Value* value;
+  bool result = Get(index, &value);
+  if (!result || !value->is_dict())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const DictionaryValue*>(value);
+
+  return true;
+}
+
+bool ListValue::GetDictionary(size_t index, DictionaryValue** out_value) {
+  return static_cast<const ListValue&>(*this).GetDictionary(
+      index, const_cast<const DictionaryValue**>(out_value));
+}
+
+bool ListValue::GetList(size_t index, const ListValue** out_value) const {
+  const Value* value;
+  bool result = Get(index, &value);
+  if (!result || !value->is_list())
+    return false;
+
+  if (out_value)
+    *out_value = static_cast<const ListValue*>(value);
+
+  return true;
+}
+
+bool ListValue::GetList(size_t index, ListValue** out_value) {
+  return static_cast<const ListValue&>(*this).GetList(
+      index, const_cast<const ListValue**>(out_value));
+}
+
+bool ListValue::Remove(size_t index, std::unique_ptr<Value>* out_value) {
+  if (index >= list_.size())
+    return false;
+
+  if (out_value)
+    *out_value = std::make_unique<Value>(std::move(list_[index]));
+
+  list_.erase(list_.begin() + index);
+  return true;
+}
+
+bool ListValue::Remove(const Value& value, size_t* index) {
+  auto it = std::find(list_.begin(), list_.end(), value);
+
+  if (it == list_.end())
+    return false;
+
+  if (index)
+    *index = std::distance(list_.begin(), it);
+
+  list_.erase(it);
+  return true;
+}
+
+ListValue::iterator ListValue::Erase(iterator iter,
+                                     std::unique_ptr<Value>* out_value) {
+  if (out_value)
+    *out_value = std::make_unique<Value>(std::move(*iter));
+
+  return list_.erase(iter);
+}
+
+void ListValue::Append(std::unique_ptr<Value> in_value) {
+  list_.push_back(std::move(*in_value));
+}
+
+void ListValue::AppendBoolean(bool in_value) {
+  list_.emplace_back(in_value);
+}
+
+void ListValue::AppendInteger(int in_value) {
+  list_.emplace_back(in_value);
+}
+
+void ListValue::AppendString(std::string_view in_value) {
+  list_.emplace_back(in_value);
+}
+
+void ListValue::AppendString(const std::u16string& in_value) {
+  list_.emplace_back(in_value);
+}
+
+void ListValue::AppendStrings(const std::vector<std::string>& in_values) {
+  list_.reserve(list_.size() + in_values.size());
+  for (const auto& in_value : in_values)
+    list_.emplace_back(in_value);
+}
+
+void ListValue::AppendStrings(const std::vector<std::u16string>& in_values) {
+  list_.reserve(list_.size() + in_values.size());
+  for (const auto& in_value : in_values)
+    list_.emplace_back(in_value);
+}
+
+bool ListValue::AppendIfNotPresent(std::unique_ptr<Value> in_value) {
+  DCHECK(in_value);
+  if (ContainsValue(list_, *in_value))
+    return false;
+
+  list_.push_back(std::move(*in_value));
+  return true;
+}
+
+bool ListValue::Insert(size_t index, std::unique_ptr<Value> in_value) {
+  DCHECK(in_value);
+  if (index > list_.size())
+    return false;
+
+  list_.insert(list_.begin() + index, std::move(*in_value));
+  return true;
+}
+
+ListValue::const_iterator ListValue::Find(const Value& value) const {
+  return std::find(list_.begin(), list_.end(), value);
+}
+
+void ListValue::Swap(ListValue* other) {
+  CHECK(other->is_list());
+  list_.swap(other->list_);
+}
+
+ListValue* ListValue::DeepCopy() const {
+  return new ListValue(list_);
+}
+
+std::unique_ptr<ListValue> ListValue::CreateDeepCopy() const {
+  return std::make_unique<ListValue>(list_);
+}
+
+ValueSerializer::~ValueSerializer() = default;
+
+ValueDeserializer::~ValueDeserializer() = default;
+
+std::ostream& operator<<(std::ostream& out, const Value& value) {
+  std::string json;
+  JSONWriter::WriteWithOptions(value, JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+  return out << json;
+}
+
+std::ostream& operator<<(std::ostream& out, const Value::Type& type) {
+  if (static_cast<int>(type) < 0 ||
+      static_cast<size_t>(type) >= std::size(kTypeNames))
+    return out << "Invalid Type (index = " << static_cast<int>(type) << ")";
+  return out << Value::GetTypeName(type);
+}
+
+}  // namespace base
diff --git a/src/base/values.h b/src/base/values.h
new file mode 100644 (file)
index 0000000..0b1dde6
--- /dev/null
@@ -0,0 +1,763 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file specifies a recursive data storage class called Value intended for
+// storing settings and other persistable data.
+//
+// A Value represents something that can be stored in JSON or passed to/from
+// JavaScript. As such, it is NOT a generalized variant type, since only the
+// types supported by JavaScript/JSON are supported.
+//
+// IN PARTICULAR this means that there is no support for int64_t or unsigned
+// numbers. Writing JSON with such types would violate the spec. If you need
+// something like this, make a string value containing the number you want.
+//
+// NOTE: A Value parameter that is always a Value::STRING should just be passed
+// as a std::string. Similarly for Values that are always Value::DICTIONARY
+// (should be flat_map), Value::LIST (should be std::vector), et cetera.
+
+#ifndef BASE_VALUES_H_
+#define BASE_VALUES_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/value_iterators.h"
+
+namespace base {
+
+class DictionaryValue;
+class ListValue;
+class Value;
+
+// The Value class is the base class for Values. A Value can be instantiated
+// via passing the appropriate type or backing storage to the constructor.
+//
+// See the file-level comment above for more information.
+//
+// base::Value is currently in the process of being refactored. Design doc:
+// https://docs.google.com/document/d/1uDLu5uTRlCWePxQUEHc8yNQdEoE1BDISYdpggWEABnw
+//
+// Previously (which is how most code that currently exists is written), Value
+// used derived types to implement the individual data types, and base::Value
+// was just a base class to refer to them. This required everything be heap
+// allocated.
+//
+// OLD WAY:
+//
+//   std::unique_ptr<base::Value> GetFoo() {
+//     std::unique_ptr<DictionaryValue> dict;
+//     dict->SetString("mykey", foo);
+//     return dict;
+//   }
+//
+// The new design makes base::Value a variant type that holds everything in
+// a union. It is now recommended to pass by value with std::move rather than
+// use heap allocated values. The DictionaryValue and ListValue subclasses
+// exist only as a compatibility shim that we're in the process of removing.
+//
+// NEW WAY:
+//
+//   base::Value GetFoo() {
+//     base::Value dict(base::Value::Type::DICTIONARY);
+//     dict.SetKey("mykey", base::Value(foo));
+//     return dict;
+//   }
+class Value {
+ public:
+  using BlobStorage = std::vector<char>;
+  using DictStorage = flat_map<std::string, std::unique_ptr<Value>>;
+  using ListStorage = std::vector<Value>;
+
+  enum class Type {
+    NONE = 0,
+    BOOLEAN,
+    INTEGER,
+    STRING,
+    BINARY,
+    DICTIONARY,
+    LIST
+    // Note: Do not add more types. See the file-level comment above for why.
+  };
+
+  // For situations where you want to keep ownership of your buffer, this
+  // factory method creates a new BinaryValue by copying the contents of the
+  // buffer that's passed in.
+  // DEPRECATED, use std::make_unique<Value>(const BlobStorage&) instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  static std::unique_ptr<Value> CreateWithCopiedBuffer(const char* buffer,
+                                                       size_t size);
+
+  // Adaptors for converting from the old way to the new way and vice versa.
+  static Value FromUniquePtrValue(std::unique_ptr<Value> val);
+  static std::unique_ptr<Value> ToUniquePtrValue(Value val);
+
+  Value(Value&& that) noexcept;
+  Value() noexcept;  // A null value.
+
+  // Value's copy constructor and copy assignment operator are deleted. Use this
+  // to obtain a deep copy explicitly.
+  Value Clone() const;
+
+  explicit Value(Type type);
+  explicit Value(bool in_bool);
+  explicit Value(int in_int);
+
+  // Value(const char*) and Value(const char16_t*) are required despite
+  // Value(std::string_view) and Value(std::u16string_view) because otherwise
+  // the compiler will choose the Value(bool) constructor for these arguments.
+  // Value(std::string&&) allow for efficient move construction.
+  explicit Value(const char* in_string);
+  explicit Value(std::string_view in_string);
+  explicit Value(std::string&& in_string) noexcept;
+  explicit Value(const char16_t* in_string16);
+  explicit Value(std::u16string_view in_string16);
+
+  explicit Value(const BlobStorage& in_blob);
+  explicit Value(BlobStorage&& in_blob) noexcept;
+
+  explicit Value(const DictStorage& in_dict);
+  explicit Value(DictStorage&& in_dict) noexcept;
+
+  explicit Value(const ListStorage& in_list);
+  explicit Value(ListStorage&& in_list) noexcept;
+
+  Value& operator=(Value&& that) noexcept;
+
+  ~Value();
+
+  // Returns the name for a given |type|.
+  static const char* GetTypeName(Type type);
+
+  // Returns the type of the value stored by the current Value object.
+  Type type() const { return type_; }
+
+  // Returns true if the current object represents a given type.
+  bool is_none() const { return type() == Type::NONE; }
+  bool is_bool() const { return type() == Type::BOOLEAN; }
+  bool is_int() const { return type() == Type::INTEGER; }
+  bool is_string() const { return type() == Type::STRING; }
+  bool is_blob() const { return type() == Type::BINARY; }
+  bool is_dict() const { return type() == Type::DICTIONARY; }
+  bool is_list() const { return type() == Type::LIST; }
+
+  // These will all fatally assert if the type doesn't match.
+  bool GetBool() const;
+  int GetInt() const;
+  const std::string& GetString() const;
+  const BlobStorage& GetBlob() const;
+
+  ListStorage& GetList();
+  const ListStorage& GetList() const;
+
+  // |FindKey| looks up |key| in the underlying dictionary. If found, it returns
+  // a pointer to the element. Otherwise it returns nullptr.
+  // returned. Callers are expected to perform a check against null before using
+  // the pointer.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  //
+  // Example:
+  //   auto* found = FindKey("foo");
+  Value* FindKey(std::string_view key);
+  const Value* FindKey(std::string_view key) const;
+
+  // |FindKeyOfType| is similar to |FindKey|, but it also requires the found
+  // value to have type |type|. If no type is found, or the found value is of a
+  // different type nullptr is returned.
+  // Callers are expected to perform a check against null before using the
+  // pointer.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  //
+  // Example:
+  //   auto* found = FindKey("foo", Type::INTEGER);
+  Value* FindKeyOfType(std::string_view key, Type type);
+  const Value* FindKeyOfType(std::string_view key, Type type) const;
+
+  // |SetKey| looks up |key| in the underlying dictionary and sets the mapped
+  // value to |value|. If |key| could not be found, a new element is inserted.
+  // A pointer to the modified item is returned.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  //
+  // Example:
+  //   SetKey("foo", std::move(myvalue));
+  Value* SetKey(std::string_view key, Value value);
+  // This overload results in a performance improvement for std::string&&.
+  Value* SetKey(std::string&& key, Value value);
+  // This overload is necessary to avoid ambiguity for const char* arguments.
+  Value* SetKey(const char* key, Value value);
+
+  // This attempts to remove the value associated with |key|. In case of failure,
+  // e.g. the key does not exist, |false| is returned and the underlying
+  // dictionary is not changed. In case of success, |key| is deleted from the
+  // dictionary and the method returns |true|.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  //
+  // Example:
+  //   bool success = RemoveKey("foo");
+  bool RemoveKey(std::string_view key);
+
+  // Searches a hierarchy of dictionary values for a given value. If a path
+  // of dictionaries exist, returns the item at that path. If any of the path
+  // components do not exist or if any but the last path components are not
+  // dictionaries, returns nullptr.
+  //
+  // The type of the leaf Value is not checked.
+  //
+  // Implementation note: This can't return an iterator because the iterator
+  // will actually be into another Value, so it can't be compared to iterators
+  // from this one (in particular, the DictItems().end() iterator).
+  //
+  // Example:
+  //   auto* found = FindPath({"foo", "bar"});
+  //
+  //   std::vector<std::string_view> components = ...
+  //   auto* found = FindPath(components);
+  //
+  // Note: If there is only one component in the path, use FindKey() instead.
+  Value* FindPath(std::initializer_list<std::string_view> path);
+  Value* FindPath(span<const std::string_view> path);
+  const Value* FindPath(std::initializer_list<std::string_view> path) const;
+  const Value* FindPath(span<const std::string_view> path) const;
+
+  // Like FindPath() but will only return the value if the leaf Value type
+  // matches the given type. Will return nullptr otherwise.
+  //
+  // Note: If there is only one component in the path, use FindKeyOfType()
+  // instead.
+  Value* FindPathOfType(std::initializer_list<std::string_view> path,
+                        Type type);
+  Value* FindPathOfType(span<const std::string_view> path, Type type);
+  const Value* FindPathOfType(std::initializer_list<std::string_view> path,
+                              Type type) const;
+  const Value* FindPathOfType(span<const std::string_view> path,
+                              Type type) const;
+
+  // Sets the given path, expanding and creating dictionary keys as necessary.
+  //
+  // If the current value is not a dictionary, the function returns nullptr. If
+  // path components do not exist, they will be created. If any but the last
+  // components matches a value that is not a dictionary, the function will fail
+  // (it will not overwrite the value) and return nullptr. The last path
+  // component will be unconditionally overwritten if it exists, and created if
+  // it doesn't.
+  //
+  // Example:
+  //   value.SetPath({"foo", "bar"}, std::move(myvalue));
+  //
+  //   std::vector<std::string_view> components = ...
+  //   value.SetPath(components, std::move(myvalue));
+  //
+  // Note: If there is only one component in the path, use SetKey() instead.
+  Value* SetPath(std::initializer_list<std::string_view> path, Value value);
+  Value* SetPath(span<const std::string_view> path, Value value);
+
+  // Tries to remove a Value at the given path.
+  //
+  // If the current value is not a dictionary or any path components does not
+  // exist, this operation fails, leaves underlying Values untouched and returns
+  // |false|. In case intermediate dictionaries become empty as a result of this
+  // path removal, they will be removed as well.
+  //
+  // Example:
+  //   bool success = value.RemovePath({"foo", "bar"});
+  //
+  //   std::vector<std::string_view> components = ...
+  //   bool success = value.RemovePath(components);
+  //
+  // Note: If there is only one component in the path, use RemoveKey() instead.
+  bool RemovePath(std::initializer_list<std::string_view> path);
+  bool RemovePath(span<const std::string_view> path);
+
+  using dict_iterator_proxy = detail::dict_iterator_proxy;
+  using const_dict_iterator_proxy = detail::const_dict_iterator_proxy;
+
+  // |DictItems| returns a proxy object that exposes iterators to the underlying
+  // dictionary. These are intended for iteration over all items in the
+  // dictionary and are compatible with for-each loops and standard library
+  // algorithms.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  dict_iterator_proxy DictItems();
+  const_dict_iterator_proxy DictItems() const;
+
+  // Returns the size of the dictionary, and if the dictionary is empty.
+  // Note: This fatally asserts if type() is not Type::DICTIONARY.
+  size_t DictSize() const;
+  bool DictEmpty() const;
+
+  // These methods allow the convenient retrieval of the contents of the Value.
+  // If the current object can be converted into the given type, the value is
+  // returned through the |out_value| parameter and true is returned;
+  // otherwise, false is returned and |out_value| is unchanged.
+  // DEPRECATED, use GetBool() instead.
+  bool GetAsBoolean(bool* out_value) const;
+  // DEPRECATED, use GetInt() instead.
+  bool GetAsInteger(int* out_value) const;
+  // DEPRECATED, use GetString() instead.
+  bool GetAsString(std::string* out_value) const;
+  bool GetAsString(std::u16string* out_value) const;
+  bool GetAsString(const Value** out_value) const;
+  bool GetAsString(std::string_view* out_value) const;
+  // ListValue::From is the equivalent for std::unique_ptr conversions.
+  // DEPRECATED, use GetList() instead.
+  bool GetAsList(ListValue** out_value);
+  bool GetAsList(const ListValue** out_value) const;
+  // DictionaryValue::From is the equivalent for std::unique_ptr conversions.
+  bool GetAsDictionary(DictionaryValue** out_value);
+  bool GetAsDictionary(const DictionaryValue** out_value) const;
+  // Note: Do not add more types. See the file-level comment above for why.
+
+  // This creates a deep copy of the entire Value tree, and returns a pointer
+  // to the copy. The caller gets ownership of the copy, of course.
+  // Subclasses return their own type directly in their overrides;
+  // this works because C++ supports covariant return types.
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  Value* DeepCopy() const;
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  std::unique_ptr<Value> CreateDeepCopy() const;
+
+  // Comparison operators so that Values can easily be used with standard
+  // library algorithms and associative containers.
+  friend bool operator==(const Value& lhs, const Value& rhs);
+  friend bool operator!=(const Value& lhs, const Value& rhs);
+  friend bool operator<(const Value& lhs, const Value& rhs);
+  friend bool operator>(const Value& lhs, const Value& rhs);
+  friend bool operator<=(const Value& lhs, const Value& rhs);
+  friend bool operator>=(const Value& lhs, const Value& rhs);
+
+  // Compares if two Value objects have equal contents.
+  // DEPRECATED, use operator==(const Value& lhs, const Value& rhs) instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  bool Equals(const Value* other) const;
+
+  // Estimates dynamic memory usage.
+  // See base/trace_event/memory_usage_estimator.h for more info.
+  size_t EstimateMemoryUsage() const;
+
+ protected:
+  // TODO(crbug.com/646113): Make these private once DictionaryValue and
+  // ListValue are properly inlined.
+  Type type_;
+
+  union {
+    bool bool_value_;
+    int int_value_;
+    std::string string_value_;
+    BlobStorage binary_value_;
+    DictStorage dict_;
+    ListStorage list_;
+  };
+
+ private:
+  void InternalMoveConstructFrom(Value&& that);
+  void InternalCleanup();
+
+  DISALLOW_COPY_AND_ASSIGN(Value);
+};
+
+// DictionaryValue provides a key-value dictionary with (optional) "path"
+// parsing for recursive access; see the comment at the top of the file. Keys
+// are |std::string|s and should be UTF-8 encoded.
+class DictionaryValue : public Value {
+ public:
+  using const_iterator = DictStorage::const_iterator;
+  using iterator = DictStorage::iterator;
+
+  // Returns |value| if it is a dictionary, nullptr otherwise.
+  static std::unique_ptr<DictionaryValue> From(std::unique_ptr<Value> value);
+
+  DictionaryValue();
+  explicit DictionaryValue(const DictStorage& in_dict);
+  explicit DictionaryValue(DictStorage&& in_dict) noexcept;
+
+  // Returns true if the current dictionary has a value for the given key.
+  // DEPRECATED, use Value::FindKey(key) instead.
+  bool HasKey(std::string_view key) const;
+
+  // Returns the number of Values in this dictionary.
+  size_t size() const { return dict_.size(); }
+
+  // Returns whether the dictionary is empty.
+  bool empty() const { return dict_.empty(); }
+
+  // Clears any current contents of this dictionary.
+  void Clear();
+
+  // Sets the Value associated with the given path starting from this object.
+  // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes
+  // into the next DictionaryValue down.  Obviously, "." can't be used
+  // within a key, but there are no other restrictions on keys.
+  // If the key at any step of the way doesn't exist, or exists but isn't
+  // a DictionaryValue, a new DictionaryValue will be created and attached
+  // to the path in that location. |in_value| must be non-null.
+  // Returns a pointer to the inserted value.
+  // DEPRECATED, use Value::SetPath(path, value) instead.
+  Value* Set(std::string_view path, std::unique_ptr<Value> in_value);
+
+  // Convenience forms of Set().  These methods will replace any existing
+  // value at that path, even if it has a different type.
+  // DEPRECATED, use Value::SetPath(path, Value(bool)) instead.
+  Value* SetBoolean(std::string_view path, bool in_value);
+  // DEPRECATED, use Value::SetPath(path, Value(int)) instead.
+  Value* SetInteger(std::string_view path, int in_value);
+  // DEPRECATED, use Value::SetPath(path, Value(std::string_view)) instead.
+  Value* SetString(std::string_view path, std::string_view in_value);
+  // DEPRECATED, use Value::SetPath(path, Value(const string& 16)) instead.
+  Value* SetString(std::string_view path, const std::u16string& in_value);
+  // DEPRECATED, use Value::SetPath(path, Value(Type::DICTIONARY)) instead.
+  DictionaryValue* SetDictionary(std::string_view path,
+                                 std::unique_ptr<DictionaryValue> in_value);
+  // DEPRECATED, use Value::SetPath(path, Value(Type::LIST)) instead.
+  ListValue* SetList(std::string_view path,
+                     std::unique_ptr<ListValue> in_value);
+
+  // Like Set(), but without special treatment of '.'.  This allows e.g. URLs to
+  // be used as paths.
+  // DEPRECATED, use Value::SetKey(key, value) instead.
+  Value* SetWithoutPathExpansion(std::string_view key,
+                                 std::unique_ptr<Value> in_value);
+
+  // Gets the Value associated with the given path starting from this object.
+  // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes
+  // into the next DictionaryValue down.  If the path can be resolved
+  // successfully, the value for the last key in the path will be returned
+  // through the |out_value| parameter, and the function will return true.
+  // Otherwise, it will return false and |out_value| will be untouched.
+  // Note that the dictionary always owns the value that's returned.
+  // |out_value| is optional and will only be set if non-NULL.
+  // DEPRECATED, use Value::FindPath(path) instead.
+  bool Get(std::string_view path, const Value** out_value) const;
+  // DEPRECATED, use Value::FindPath(path) instead.
+  bool Get(std::string_view path, Value** out_value);
+
+  // These are convenience forms of Get().  The value will be retrieved
+  // and the return value will be true if the path is valid and the value at
+  // the end of the path can be returned in the form specified.
+  // |out_value| is optional and will only be set if non-NULL.
+  // DEPRECATED, use Value::FindPath(path) and Value::GetBool() instead.
+  bool GetBoolean(std::string_view path, bool* out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetInt() instead.
+  bool GetInteger(std::string_view path, int* out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
+  bool GetString(std::string_view path, std::string* out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
+  bool GetString(std::string_view path, std::u16string* out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
+  bool GetStringASCII(std::string_view path, std::string* out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetBlob() instead.
+  bool GetBinary(std::string_view path, const Value** out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetBlob() instead.
+  bool GetBinary(std::string_view path, Value** out_value);
+  // DEPRECATED, use Value::FindPath(path) and Value's Dictionary API instead.
+  bool GetDictionary(std::string_view path,
+                     const DictionaryValue** out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value's Dictionary API instead.
+  bool GetDictionary(std::string_view path, DictionaryValue** out_value);
+  // DEPRECATED, use Value::FindPath(path) and Value::GetList() instead.
+  bool GetList(std::string_view path, const ListValue** out_value) const;
+  // DEPRECATED, use Value::FindPath(path) and Value::GetList() instead.
+  bool GetList(std::string_view path, ListValue** out_value);
+
+  // Like Get(), but without special treatment of '.'.  This allows e.g. URLs to
+  // be used as paths.
+  // DEPRECATED, use Value::FindKey(key) instead.
+  bool GetWithoutPathExpansion(std::string_view key,
+                               const Value** out_value) const;
+  // DEPRECATED, use Value::FindKey(key) instead.
+  bool GetWithoutPathExpansion(std::string_view key, Value** out_value);
+  // DEPRECATED, use Value::FindKey(key) and Value::GetBool() instead.
+  bool GetBooleanWithoutPathExpansion(std::string_view key,
+                                      bool* out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value::GetInt() instead.
+  bool GetIntegerWithoutPathExpansion(std::string_view key,
+                                      int* out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value::GetString() instead.
+  bool GetStringWithoutPathExpansion(std::string_view key,
+                                     std::string* out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value::GetString() instead.
+  bool GetStringWithoutPathExpansion(std::string_view key,
+                                     std::u16string* out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value's Dictionary API instead.
+  bool GetDictionaryWithoutPathExpansion(
+      std::string_view key,
+      const DictionaryValue** out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value's Dictionary API instead.
+  bool GetDictionaryWithoutPathExpansion(std::string_view key,
+                                         DictionaryValue** out_value);
+  // DEPRECATED, use Value::FindKey(key) and Value::GetList() instead.
+  bool GetListWithoutPathExpansion(std::string_view key,
+                                   const ListValue** out_value) const;
+  // DEPRECATED, use Value::FindKey(key) and Value::GetList() instead.
+  bool GetListWithoutPathExpansion(std::string_view key, ListValue** out_value);
+
+  // Removes the Value with the specified path from this dictionary (or one
+  // of its child dictionaries, if the path is more than just a local key).
+  // If |out_value| is non-NULL, the removed Value will be passed out via
+  // |out_value|.  If |out_value| is NULL, the removed value will be deleted.
+  // This method returns true if |path| is a valid path; otherwise it will
+  // return false and the DictionaryValue object will be unchanged.
+  // DEPRECATED, use Value::RemovePath(path) instead.
+  bool Remove(std::string_view path, std::unique_ptr<Value>* out_value);
+
+  // Like Remove(), but without special treatment of '.'.  This allows e.g. URLs
+  // to be used as paths.
+  // DEPRECATED, use Value::RemoveKey(key) instead.
+  bool RemoveWithoutPathExpansion(std::string_view key,
+                                  std::unique_ptr<Value>* out_value);
+
+  // Removes a path, clearing out all dictionaries on |path| that remain empty
+  // after removing the value at |path|.
+  // DEPRECATED, use Value::RemovePath(path) instead.
+  bool RemovePath(std::string_view path, std::unique_ptr<Value>* out_value);
+
+  using Value::RemovePath;  // DictionaryValue::RemovePath shadows otherwise.
+
+  // Makes a copy of |this| but doesn't include empty dictionaries and lists in
+  // the copy.  This never returns NULL, even if |this| itself is empty.
+  std::unique_ptr<DictionaryValue> DeepCopyWithoutEmptyChildren() const;
+
+  // Merge |dictionary| into this dictionary. This is done recursively, i.e. any
+  // sub-dictionaries will be merged as well. In case of key collisions, the
+  // passed in dictionary takes precedence and data already present will be
+  // replaced. Values within |dictionary| are deep-copied, so |dictionary| may
+  // be freed any time after this call.
+  void MergeDictionary(const DictionaryValue* dictionary);
+
+  // Swaps contents with the |other| dictionary.
+  void Swap(DictionaryValue* other);
+
+  // This class provides an iterator over both keys and values in the
+  // dictionary.  It can't be used to modify the dictionary.
+  // DEPRECATED, use Value::DictItems() instead.
+  class Iterator {
+   public:
+    explicit Iterator(const DictionaryValue& target);
+    Iterator(const Iterator& other);
+    ~Iterator();
+
+    bool IsAtEnd() const { return it_ == target_.dict_.end(); }
+    void Advance() { ++it_; }
+
+    const std::string& key() const { return it_->first; }
+    const Value& value() const { return *it_->second; }
+
+   private:
+    const DictionaryValue& target_;
+    DictStorage::const_iterator it_;
+  };
+
+  // Iteration.
+  // DEPRECATED, use Value::DictItems() instead.
+  iterator begin() { return dict_.begin(); }
+  iterator end() { return dict_.end(); }
+
+  // DEPRECATED, use Value::DictItems() instead.
+  const_iterator begin() const { return dict_.begin(); }
+  const_iterator end() const { return dict_.end(); }
+
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  DictionaryValue* DeepCopy() const;
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  std::unique_ptr<DictionaryValue> CreateDeepCopy() const;
+};
+
+// This type of Value represents a list of other Value values.
+class ListValue : public Value {
+ public:
+  using const_iterator = ListStorage::const_iterator;
+  using iterator = ListStorage::iterator;
+
+  // Returns |value| if it is a list, nullptr otherwise.
+  static std::unique_ptr<ListValue> From(std::unique_ptr<Value> value);
+
+  ListValue();
+  explicit ListValue(const ListStorage& in_list);
+  explicit ListValue(ListStorage&& in_list) noexcept;
+
+  // Clears the contents of this ListValue
+  // DEPRECATED, use GetList()::clear() instead.
+  void Clear();
+
+  // Returns the number of Values in this list.
+  // DEPRECATED, use GetList()::size() instead.
+  size_t GetSize() const { return list_.size(); }
+
+  // Returns whether the list is empty.
+  // DEPRECATED, use GetList()::empty() instead.
+  bool empty() const { return list_.empty(); }
+
+  // Reserves storage for at least |n| values.
+  // DEPRECATED, use GetList()::reserve() instead.
+  void Reserve(size_t n);
+
+  // Sets the list item at the given index to be the Value specified by
+  // the value given.  If the index beyond the current end of the list, null
+  // Values will be used to pad out the list.
+  // Returns true if successful, or false if the index was negative or
+  // the value is a null pointer.
+  // DEPRECATED, use GetList()::operator[] instead.
+  bool Set(size_t index, std::unique_ptr<Value> in_value);
+
+  // Gets the Value at the given index.  Modifies |out_value| (and returns true)
+  // only if the index falls within the current list range.
+  // Note that the list always owns the Value passed out via |out_value|.
+  // |out_value| is optional and will only be set if non-NULL.
+  // DEPRECATED, use GetList()::operator[] instead.
+  bool Get(size_t index, const Value** out_value) const;
+  bool Get(size_t index, Value** out_value);
+
+  // Convenience forms of Get().  Modifies |out_value| (and returns true)
+  // only if the index is valid and the Value at that index can be returned
+  // in the specified form.
+  // |out_value| is optional and will only be set if non-NULL.
+  // DEPRECATED, use GetList()::operator[]::GetBool() instead.
+  bool GetBoolean(size_t index, bool* out_value) const;
+  // DEPRECATED, use GetList()::operator[]::GetInt() instead.
+  bool GetInteger(size_t index, int* out_value) const;
+  // DEPRECATED, use GetList()::operator[]::GetString() instead.
+  bool GetString(size_t index, std::string* out_value) const;
+  bool GetString(size_t index, std::u16string* out_value) const;
+
+  bool GetDictionary(size_t index, const DictionaryValue** out_value) const;
+  bool GetDictionary(size_t index, DictionaryValue** out_value);
+
+  using Value::GetList;
+  // DEPRECATED, use GetList()::operator[]::GetList() instead.
+  bool GetList(size_t index, const ListValue** out_value) const;
+  bool GetList(size_t index, ListValue** out_value);
+
+  // Removes the Value with the specified index from this list.
+  // If |out_value| is non-NULL, the removed Value AND ITS OWNERSHIP will be
+  // passed out via |out_value|.  If |out_value| is NULL, the removed value will
+  // be deleted.  This method returns true if |index| is valid; otherwise
+  // it will return false and the ListValue object will be unchanged.
+  // DEPRECATED, use GetList()::erase() instead.
+  bool Remove(size_t index, std::unique_ptr<Value>* out_value);
+
+  // Removes the first instance of |value| found in the list, if any, and
+  // deletes it. |index| is the location where |value| was found. Returns false
+  // if not found.
+  // DEPRECATED, use GetList()::erase() instead.
+  bool Remove(const Value& value, size_t* index);
+
+  // Removes the element at |iter|. If |out_value| is NULL, the value will be
+  // deleted, otherwise ownership of the value is passed back to the caller.
+  // Returns an iterator pointing to the location of the element that
+  // followed the erased element.
+  // DEPRECATED, use GetList()::erase() instead.
+  iterator Erase(iterator iter, std::unique_ptr<Value>* out_value);
+
+  // Appends a Value to the end of the list.
+  // DEPRECATED, use GetList()::push_back() instead.
+  void Append(std::unique_ptr<Value> in_value);
+
+  // Convenience forms of Append.
+  // DEPRECATED, use GetList()::emplace_back() instead.
+  void AppendBoolean(bool in_value);
+  void AppendInteger(int in_value);
+  void AppendString(std::string_view in_value);
+  void AppendString(const std::u16string& in_value);
+  // DEPRECATED, use GetList()::emplace_back() in a loop instead.
+  void AppendStrings(const std::vector<std::string>& in_values);
+  void AppendStrings(const std::vector<std::u16string>& in_values);
+
+  // Appends a Value if it's not already present. Returns true if successful,
+  // or false if the value was already
+  // DEPRECATED, use std::find() with GetList()::push_back() instead.
+  bool AppendIfNotPresent(std::unique_ptr<Value> in_value);
+
+  // Insert a Value at index.
+  // Returns true if successful, or false if the index was out of range.
+  // DEPRECATED, use GetList()::insert() instead.
+  bool Insert(size_t index, std::unique_ptr<Value> in_value);
+
+  // Searches for the first instance of |value| in the list using the Equals
+  // method of the Value type.
+  // Returns a const_iterator to the found item or to end() if none exists.
+  // DEPRECATED, use std::find() instead.
+  const_iterator Find(const Value& value) const;
+
+  // Swaps contents with the |other| list.
+  // DEPRECATED, use GetList()::swap() instead.
+  void Swap(ListValue* other);
+
+  // Iteration.
+  // DEPRECATED, use GetList()::begin() instead.
+  iterator begin() { return list_.begin(); }
+  // DEPRECATED, use GetList()::end() instead.
+  iterator end() { return list_.end(); }
+
+  // DEPRECATED, use GetList()::begin() instead.
+  const_iterator begin() const { return list_.begin(); }
+  // DEPRECATED, use GetList()::end() instead.
+  const_iterator end() const { return list_.end(); }
+
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  ListValue* DeepCopy() const;
+  // DEPRECATED, use Value::Clone() instead.
+  // TODO(crbug.com/646113): Delete this and migrate callsites.
+  std::unique_ptr<ListValue> CreateDeepCopy() const;
+};
+
+// This interface is implemented by classes that know how to serialize
+// Value objects.
+class ValueSerializer {
+ public:
+  virtual ~ValueSerializer();
+
+  virtual bool Serialize(const Value& root) = 0;
+};
+
+// This interface is implemented by classes that know how to deserialize Value
+// objects.
+class ValueDeserializer {
+ public:
+  virtual ~ValueDeserializer();
+
+  // This method deserializes the subclass-specific format into a Value object.
+  // If the return value is non-NULL, the caller takes ownership of returned
+  // Value. If the return value is NULL, and if error_code is non-NULL,
+  // error_code will be set with the underlying error.
+  // If |error_message| is non-null, it will be filled in with a formatted
+  // error message including the location of the error if appropriate.
+  virtual std::unique_ptr<Value> Deserialize(int* error_code,
+                                             std::string* error_str) = 0;
+};
+
+// Stream operator so Values can be used in assertion statements.  In order that
+// gtest uses this operator to print readable output on test failures, we must
+// override each specific type. Otherwise, the default template implementation
+// is preferred over an upcast.
+std::ostream& operator<<(std::ostream& out, const Value& value);
+
+inline std::ostream& operator<<(std::ostream& out,
+                                const DictionaryValue& value) {
+  return out << static_cast<const Value&>(value);
+}
+
+inline std::ostream& operator<<(std::ostream& out, const ListValue& value) {
+  return out << static_cast<const Value&>(value);
+}
+
+// Stream operator so that enum class Types can be used in log statements.
+std::ostream& operator<<(std::ostream& out, const Value::Type& type);
+
+}  // namespace base
+
+#endif  // BASE_VALUES_H_
diff --git a/src/base/win/registry.cc b/src/base/win/registry.cc
new file mode 100644 (file)
index 0000000..5acfda7
--- /dev/null
@@ -0,0 +1,623 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/win/registry.h"
+
+#include <shlwapi.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/win/win_util.h"
+
+namespace base {
+namespace win {
+
+namespace {
+
+// RegEnumValue() reports the number of characters from the name that were
+// written to the buffer, not how many there are. This constant is the maximum
+// name size, such that a buffer with this size should read any name.
+const DWORD MAX_REGISTRY_NAME_SIZE = 16384;
+
+// Registry values are read as BYTE* but can have char16_t* data whose last
+// char16_t is truncated. This function converts the reported |byte_size| to
+// a size in char16_t that can store a truncated char16_t if necessary.
+inline DWORD to_wchar_size(DWORD byte_size) {
+  return (byte_size + sizeof(char16_t) - 1) / sizeof(char16_t);
+}
+
+// Mask to pull WOW64 access flags out of REGSAM access.
+const REGSAM kWow64AccessMask = KEY_WOW64_32KEY | KEY_WOW64_64KEY;
+
+}  // namespace
+
+// RegKey ----------------------------------------------------------------------
+
+RegKey::RegKey() : key_(NULL), wow64access_(0) {}
+
+RegKey::RegKey(HKEY key) : key_(key), wow64access_(0) {}
+
+RegKey::RegKey(HKEY rootkey, const char16_t* subkey, REGSAM access)
+    : key_(NULL), wow64access_(0) {
+  if (rootkey) {
+    if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK))
+      Create(rootkey, subkey, access);
+    else
+      Open(rootkey, subkey, access);
+  } else {
+    DCHECK(!subkey);
+    wow64access_ = access & kWow64AccessMask;
+  }
+}
+
+RegKey::~RegKey() {
+  Close();
+}
+
+LONG RegKey::Create(HKEY rootkey, const char16_t* subkey, REGSAM access) {
+  DWORD disposition_value;
+  return CreateWithDisposition(rootkey, subkey, &disposition_value, access);
+}
+
+LONG RegKey::CreateWithDisposition(HKEY rootkey,
+                                   const char16_t* subkey,
+                                   DWORD* disposition,
+                                   REGSAM access) {
+  DCHECK(rootkey && subkey && access && disposition);
+  HKEY subhkey = NULL;
+  LONG result = RegCreateKeyEx(rootkey, ToWCharT(subkey), 0, NULL,
+                               REG_OPTION_NON_VOLATILE, access, NULL, &subhkey,
+                               disposition);
+  if (result == ERROR_SUCCESS) {
+    Close();
+    key_ = subhkey;
+    wow64access_ = access & kWow64AccessMask;
+  }
+
+  return result;
+}
+
+LONG RegKey::CreateKey(const char16_t* name, REGSAM access) {
+  DCHECK(name && access);
+  // After the application has accessed an alternate registry view using one of
+  // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations
+  // (create, delete, or open) on child registry keys must explicitly use the
+  // same flag. Otherwise, there can be unexpected behavior.
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
+  if ((access & kWow64AccessMask) != wow64access_) {
+    NOTREACHED();
+    return ERROR_INVALID_PARAMETER;
+  }
+  HKEY subkey = NULL;
+  LONG result =
+      RegCreateKeyEx(key_, ToWCharT(name), 0, NULL, REG_OPTION_NON_VOLATILE,
+                     access, NULL, &subkey, NULL);
+  if (result == ERROR_SUCCESS) {
+    Close();
+    key_ = subkey;
+    wow64access_ = access & kWow64AccessMask;
+  }
+
+  return result;
+}
+
+LONG RegKey::Open(HKEY rootkey, const char16_t* subkey, REGSAM access) {
+  DCHECK(rootkey && subkey && access);
+  HKEY subhkey = NULL;
+
+  LONG result = RegOpenKeyEx(rootkey, ToWCharT(subkey), 0, access, &subhkey);
+  if (result == ERROR_SUCCESS) {
+    Close();
+    key_ = subhkey;
+    wow64access_ = access & kWow64AccessMask;
+  }
+
+  return result;
+}
+
+LONG RegKey::OpenKey(const char16_t* relative_key_name, REGSAM access) {
+  DCHECK(relative_key_name && access);
+  // After the application has accessed an alternate registry view using one of
+  // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations
+  // (create, delete, or open) on child registry keys must explicitly use the
+  // same flag. Otherwise, there can be unexpected behavior.
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
+  if ((access & kWow64AccessMask) != wow64access_) {
+    NOTREACHED();
+    return ERROR_INVALID_PARAMETER;
+  }
+  HKEY subkey = NULL;
+  LONG result =
+      RegOpenKeyEx(key_, ToWCharT(relative_key_name), 0, access, &subkey);
+
+  // We have to close the current opened key before replacing it with the new
+  // one.
+  if (result == ERROR_SUCCESS) {
+    Close();
+    key_ = subkey;
+    wow64access_ = access & kWow64AccessMask;
+  }
+  return result;
+}
+
+void RegKey::Close() {
+  if (key_) {
+    ::RegCloseKey(key_);
+    key_ = NULL;
+    wow64access_ = 0;
+  }
+}
+
+// TODO(wfh): Remove this and other unsafe methods. See http://crbug.com/375400
+void RegKey::Set(HKEY key) {
+  if (key_ != key) {
+    Close();
+    key_ = key;
+  }
+}
+
+HKEY RegKey::Take() {
+  DCHECK_EQ(wow64access_, 0u);
+  HKEY key = key_;
+  key_ = NULL;
+  return key;
+}
+
+bool RegKey::HasValue(const char16_t* name) const {
+  return RegQueryValueEx(key_, ToWCharT(name), 0, NULL, NULL, NULL) ==
+         ERROR_SUCCESS;
+}
+
+DWORD RegKey::GetValueCount() const {
+  DWORD count = 0;
+  LONG result = RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count,
+                                NULL, NULL, NULL, NULL);
+  return (result == ERROR_SUCCESS) ? count : 0;
+}
+
+LONG RegKey::GetValueNameAt(int index, std::u16string* name) const {
+  char16_t buf[256];
+  DWORD bufsize = std::size(buf);
+  LONG r = ::RegEnumValue(key_, index, ToWCharT(buf), &bufsize, NULL, NULL,
+                          NULL, NULL);
+  if (r == ERROR_SUCCESS)
+    *name = buf;
+
+  return r;
+}
+
+LONG RegKey::DeleteKey(const char16_t* name) {
+  DCHECK(key_);
+  DCHECK(name);
+  HKEY subkey = NULL;
+
+  // Verify the key exists before attempting delete to replicate previous
+  // behavior.
+  LONG result = RegOpenKeyEx(key_, ToWCharT(name), 0,
+                             READ_CONTROL | wow64access_, &subkey);
+  if (result != ERROR_SUCCESS)
+    return result;
+  RegCloseKey(subkey);
+
+  return RegDelRecurse(key_, std::u16string(name), wow64access_);
+}
+
+LONG RegKey::DeleteEmptyKey(const char16_t* name) {
+  DCHECK(key_);
+  DCHECK(name);
+
+  HKEY target_key = NULL;
+  LONG result = RegOpenKeyEx(key_, ToWCharT(name), 0, KEY_READ | wow64access_,
+                             &target_key);
+
+  if (result != ERROR_SUCCESS)
+    return result;
+
+  DWORD count = 0;
+  result = RegQueryInfoKey(target_key, NULL, 0, NULL, NULL, NULL, NULL, &count,
+                           NULL, NULL, NULL, NULL);
+
+  RegCloseKey(target_key);
+
+  if (result != ERROR_SUCCESS)
+    return result;
+
+  if (count == 0)
+    return RegDeleteKeyExWrapper(key_, name, wow64access_, 0);
+
+  return ERROR_DIR_NOT_EMPTY;
+}
+
+LONG RegKey::DeleteValue(const char16_t* value_name) {
+  DCHECK(key_);
+  LONG result = RegDeleteValue(key_, ToWCharT(value_name));
+  return result;
+}
+
+LONG RegKey::ReadValueDW(const char16_t* name, DWORD* out_value) const {
+  DCHECK(out_value);
+  DWORD type = REG_DWORD;
+  DWORD size = sizeof(DWORD);
+  DWORD local_value = 0;
+  LONG result = ReadValue(name, &local_value, &size, &type);
+  if (result == ERROR_SUCCESS) {
+    if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD))
+      *out_value = local_value;
+    else
+      result = ERROR_CANTREAD;
+  }
+
+  return result;
+}
+
+LONG RegKey::ReadInt64(const char16_t* name, int64_t* out_value) const {
+  DCHECK(out_value);
+  DWORD type = REG_QWORD;
+  int64_t local_value = 0;
+  DWORD size = sizeof(local_value);
+  LONG result = ReadValue(name, &local_value, &size, &type);
+  if (result == ERROR_SUCCESS) {
+    if ((type == REG_QWORD || type == REG_BINARY) &&
+        size == sizeof(local_value))
+      *out_value = local_value;
+    else
+      result = ERROR_CANTREAD;
+  }
+
+  return result;
+}
+
+LONG RegKey::ReadValue(const char16_t* name, std::u16string* out_value) const {
+  DCHECK(out_value);
+  const size_t kMaxStringLength = 1024;  // This is after expansion.
+  // Use the one of the other forms of ReadValue if 1024 is too small for you.
+  char16_t raw_value[kMaxStringLength];
+  DWORD type = REG_SZ, size = sizeof(raw_value);
+  LONG result = ReadValue(name, raw_value, &size, &type);
+  if (result == ERROR_SUCCESS) {
+    if (type == REG_SZ) {
+      *out_value = raw_value;
+    } else if (type == REG_EXPAND_SZ) {
+      char16_t expanded[kMaxStringLength];
+      size = ExpandEnvironmentStrings(ToWCharT(raw_value), ToWCharT(expanded),
+                                      kMaxStringLength);
+      // Success: returns the number of char16_t's copied
+      // Fail: buffer too small, returns the size required
+      // Fail: other, returns 0
+      if (size == 0 || size > kMaxStringLength) {
+        result = ERROR_MORE_DATA;
+      } else {
+        *out_value = expanded;
+      }
+    } else {
+      // Not a string. Oops.
+      result = ERROR_CANTREAD;
+    }
+  }
+
+  return result;
+}
+
+LONG RegKey::ReadValue(const char16_t* name,
+                       void* data,
+                       DWORD* dsize,
+                       DWORD* dtype) const {
+  LONG result = RegQueryValueEx(key_, ToWCharT(name), 0, dtype,
+                                reinterpret_cast<LPBYTE>(data), dsize);
+  return result;
+}
+
+LONG RegKey::ReadValues(const char16_t* name,
+                        std::vector<std::u16string>* values) {
+  values->clear();
+
+  DWORD type = REG_MULTI_SZ;
+  DWORD size = 0;
+  LONG result = ReadValue(name, NULL, &size, &type);
+  if (result != ERROR_SUCCESS || size == 0)
+    return result;
+
+  if (type != REG_MULTI_SZ)
+    return ERROR_CANTREAD;
+
+  std::vector<char16_t> buffer(size / sizeof(char16_t));
+  result = ReadValue(name, &buffer[0], &size, NULL);
+  if (result != ERROR_SUCCESS || size == 0)
+    return result;
+
+  // Parse the double-null-terminated list of strings.
+  // Note: This code is paranoid to not read outside of |buf|, in the case where
+  // it may not be properly terminated.
+  const char16_t* entry = &buffer[0];
+  const char16_t* buffer_end = entry + (size / sizeof(char16_t));
+  while (entry < buffer_end && entry[0] != '\0') {
+    const char16_t* entry_end = std::find(entry, buffer_end, L'\0');
+    values->push_back(std::u16string(entry, entry_end));
+    entry = entry_end + 1;
+  }
+  return 0;
+}
+
+LONG RegKey::WriteValue(const char16_t* name, DWORD in_value) {
+  return WriteValue(name, &in_value, static_cast<DWORD>(sizeof(in_value)),
+                    REG_DWORD);
+}
+
+LONG RegKey::WriteValue(const char16_t* name, const char16_t* in_value) {
+  return WriteValue(
+      name, in_value,
+      static_cast<DWORD>(sizeof(*in_value) * (wcslen(ToWCharT(in_value)) + 1)),
+      REG_SZ);
+}
+
+LONG RegKey::WriteValue(const char16_t* name,
+                        const void* data,
+                        DWORD dsize,
+                        DWORD dtype) {
+  DCHECK(data || !dsize);
+
+  LONG result =
+      RegSetValueEx(key_, ToWCharT(name), 0, dtype,
+                    reinterpret_cast<LPBYTE>(const_cast<void*>(data)), dsize);
+  return result;
+}
+
+// static
+LONG RegKey::RegDeleteKeyExWrapper(HKEY hKey,
+                                   const char16_t* lpSubKey,
+                                   REGSAM samDesired,
+                                   DWORD Reserved) {
+  typedef LSTATUS(WINAPI * RegDeleteKeyExPtr)(HKEY, LPCWSTR, REGSAM, DWORD);
+
+  RegDeleteKeyExPtr reg_delete_key_ex_func =
+      reinterpret_cast<RegDeleteKeyExPtr>(
+          GetProcAddress(GetModuleHandleA("advapi32.dll"), "RegDeleteKeyExW"));
+
+  if (reg_delete_key_ex_func)
+    return reg_delete_key_ex_func(hKey, ToWCharT(lpSubKey), samDesired,
+                                  Reserved);
+
+  // Windows XP does not support RegDeleteKeyEx, so fallback to RegDeleteKey.
+  return RegDeleteKey(hKey, ToWCharT(lpSubKey));
+}
+
+// static
+LONG RegKey::RegDelRecurse(HKEY root_key,
+                           const std::u16string& name,
+                           REGSAM access) {
+  // First, see if the key can be deleted without having to recurse.
+  LONG result = RegDeleteKeyExWrapper(root_key, name.c_str(), access, 0);
+  if (result == ERROR_SUCCESS)
+    return result;
+
+  HKEY target_key = NULL;
+  result = RegOpenKeyEx(root_key, ToWCharT(&name), 0,
+                        KEY_ENUMERATE_SUB_KEYS | access, &target_key);
+
+  if (result == ERROR_FILE_NOT_FOUND)
+    return ERROR_SUCCESS;
+  if (result != ERROR_SUCCESS)
+    return result;
+
+  std::u16string subkey_name(name);
+
+  // Check for an ending slash and add one if it is missing.
+  if (!name.empty() && subkey_name[name.length() - 1] != '\\')
+    subkey_name += u"\\";
+
+  // Enumerate the keys
+  result = ERROR_SUCCESS;
+  const DWORD kMaxKeyNameLength = MAX_PATH;
+  const size_t base_key_length = subkey_name.length();
+  std::u16string key_name;
+  while (result == ERROR_SUCCESS) {
+    DWORD key_size = kMaxKeyNameLength;
+    result = RegEnumKeyEx(target_key, 0,
+                          ToWCharT(WriteInto(&key_name, kMaxKeyNameLength)),
+                          &key_size, NULL, NULL, NULL, NULL);
+
+    if (result != ERROR_SUCCESS)
+      break;
+
+    key_name.resize(key_size);
+    subkey_name.resize(base_key_length);
+    subkey_name += key_name;
+
+    if (RegDelRecurse(root_key, subkey_name, access) != ERROR_SUCCESS)
+      break;
+  }
+
+  RegCloseKey(target_key);
+
+  // Try again to delete the key.
+  result = RegDeleteKeyExWrapper(root_key, name.c_str(), access, 0);
+
+  return result;
+}
+
+// RegistryValueIterator ------------------------------------------------------
+
+RegistryValueIterator::RegistryValueIterator(HKEY root_key,
+                                             const char16_t* folder_key,
+                                             REGSAM wow64access)
+    : name_(MAX_PATH, L'\0'), value_(MAX_PATH, L'\0') {
+  Initialize(root_key, folder_key, wow64access);
+}
+
+RegistryValueIterator::RegistryValueIterator(HKEY root_key,
+                                             const char16_t* folder_key)
+    : name_(MAX_PATH, L'\0'), value_(MAX_PATH, L'\0') {
+  Initialize(root_key, folder_key, 0);
+}
+
+void RegistryValueIterator::Initialize(HKEY root_key,
+                                       const char16_t* folder_key,
+                                       REGSAM wow64access) {
+  DCHECK_EQ(wow64access & ~kWow64AccessMask, static_cast<REGSAM>(0));
+  LONG result = RegOpenKeyEx(root_key, ToWCharT(folder_key), 0,
+                             KEY_READ | wow64access, &key_);
+  if (result != ERROR_SUCCESS) {
+    key_ = NULL;
+  } else {
+    DWORD count = 0;
+    result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count,
+                               NULL, NULL, NULL, NULL);
+
+    if (result != ERROR_SUCCESS) {
+      ::RegCloseKey(key_);
+      key_ = NULL;
+    } else {
+      index_ = count - 1;
+    }
+  }
+
+  Read();
+}
+
+RegistryValueIterator::~RegistryValueIterator() {
+  if (key_)
+    ::RegCloseKey(key_);
+}
+
+DWORD RegistryValueIterator::ValueCount() const {
+  DWORD count = 0;
+  LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count,
+                                  NULL, NULL, NULL, NULL);
+  if (result != ERROR_SUCCESS)
+    return 0;
+
+  return count;
+}
+
+bool RegistryValueIterator::Valid() const {
+  return key_ != NULL && index_ >= 0;
+}
+
+void RegistryValueIterator::operator++() {
+  --index_;
+  Read();
+}
+
+bool RegistryValueIterator::Read() {
+  if (Valid()) {
+    DWORD capacity = static_cast<DWORD>(name_.capacity());
+    DWORD name_size = capacity;
+    // |value_size_| is in bytes. Reserve the last character for a NUL.
+    value_size_ = static_cast<DWORD>((value_.size() - 1) * sizeof(char16_t));
+    LONG result = ::RegEnumValue(
+        key_, index_, ToWCharT(WriteInto(&name_, name_size)), &name_size, NULL,
+        &type_, reinterpret_cast<BYTE*>(value_.data()), &value_size_);
+
+    if (result == ERROR_MORE_DATA) {
+      // Registry key names are limited to 255 characters and fit within
+      // MAX_PATH (which is 260) but registry value names can use up to 16,383
+      // characters and the value itself is not limited
+      // (from http://msdn.microsoft.com/en-us/library/windows/desktop/
+      // ms724872(v=vs.85).aspx).
+      // Resize the buffers and retry if their size caused the failure.
+      DWORD value_size_in_wchars = to_wchar_size(value_size_);
+      if (value_size_in_wchars + 1 > value_.size())
+        value_.resize(value_size_in_wchars + 1, L'\0');
+      value_size_ = static_cast<DWORD>((value_.size() - 1) * sizeof(char16_t));
+      name_size = name_size == capacity ? MAX_REGISTRY_NAME_SIZE : capacity;
+      result = ::RegEnumValue(
+          key_, index_, ToWCharT(WriteInto(&name_, name_size)), &name_size,
+          NULL, &type_, reinterpret_cast<BYTE*>(value_.data()), &value_size_);
+    }
+
+    if (result == ERROR_SUCCESS) {
+      DCHECK_LT(to_wchar_size(value_size_), value_.size());
+      value_[to_wchar_size(value_size_)] = L'\0';
+      return true;
+    }
+  }
+
+  name_[0] = L'\0';
+  value_[0] = L'\0';
+  value_size_ = 0;
+  return false;
+}
+
+// RegistryKeyIterator --------------------------------------------------------
+
+RegistryKeyIterator::RegistryKeyIterator(HKEY root_key,
+                                         const char16_t* folder_key) {
+  Initialize(root_key, folder_key, 0);
+}
+
+RegistryKeyIterator::RegistryKeyIterator(HKEY root_key,
+                                         const char16_t* folder_key,
+                                         REGSAM wow64access) {
+  Initialize(root_key, folder_key, wow64access);
+}
+
+RegistryKeyIterator::~RegistryKeyIterator() {
+  if (key_)
+    ::RegCloseKey(key_);
+}
+
+DWORD RegistryKeyIterator::SubkeyCount() const {
+  DWORD count = 0;
+  LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, NULL,
+                                  NULL, NULL, NULL, NULL);
+  if (result != ERROR_SUCCESS)
+    return 0;
+
+  return count;
+}
+
+bool RegistryKeyIterator::Valid() const {
+  return key_ != NULL && index_ >= 0;
+}
+
+void RegistryKeyIterator::operator++() {
+  --index_;
+  Read();
+}
+
+bool RegistryKeyIterator::Read() {
+  if (Valid()) {
+    DWORD ncount = std::size(name_);
+    FILETIME written;
+    LONG r = ::RegEnumKeyEx(key_, index_, ToWCharT(name_), &ncount, NULL, NULL,
+                            NULL, &written);
+    if (ERROR_SUCCESS == r)
+      return true;
+  }
+
+  name_[0] = '\0';
+  return false;
+}
+
+void RegistryKeyIterator::Initialize(HKEY root_key,
+                                     const char16_t* folder_key,
+                                     REGSAM wow64access) {
+  DCHECK_EQ(wow64access & ~kWow64AccessMask, static_cast<REGSAM>(0));
+  LONG result = RegOpenKeyEx(root_key, ToWCharT(folder_key), 0,
+                             KEY_READ | wow64access, &key_);
+  if (result != ERROR_SUCCESS) {
+    key_ = NULL;
+  } else {
+    DWORD count = 0;
+    result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, NULL,
+                               NULL, NULL, NULL, NULL);
+
+    if (result != ERROR_SUCCESS) {
+      ::RegCloseKey(key_);
+      key_ = NULL;
+    } else {
+      index_ = count - 1;
+    }
+  }
+
+  Read();
+}
+
+}  // namespace win
+}  // namespace base
diff --git a/src/base/win/registry.h b/src/base/win/registry.h
new file mode 100644 (file)
index 0000000..8e5de46
--- /dev/null
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_WIN_REGISTRY_H_
+#define BASE_WIN_REGISTRY_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <windows.h>
+
+#include "base/macros.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+namespace win {
+
+// Utility class to read, write and manipulate the Windows Registry.
+// Registry vocabulary primer: a "key" is like a folder, in which there
+// are "values", which are <name, data> pairs, with an associated data type.
+//
+// Note:
+//  * ReadValue family of functions guarantee that the out-parameter
+//    is not touched in case of failure.
+//  * Functions returning LONG indicate success as ERROR_SUCCESS or an
+//    error as a (non-zero) win32 error code.
+class RegKey {
+ public:
+  RegKey();
+  explicit RegKey(HKEY key);
+  RegKey(HKEY rootkey, const char16_t* subkey, REGSAM access);
+  ~RegKey();
+
+  LONG Create(HKEY rootkey, const char16_t* subkey, REGSAM access);
+
+  LONG CreateWithDisposition(HKEY rootkey,
+                             const char16_t* subkey,
+                             DWORD* disposition,
+                             REGSAM access);
+
+  // Creates a subkey or open it if it already exists.
+  LONG CreateKey(const char16_t* name, REGSAM access);
+
+  // Opens an existing reg key.
+  LONG Open(HKEY rootkey, const char16_t* subkey, REGSAM access);
+
+  // Opens an existing reg key, given the relative key name.
+  LONG OpenKey(const char16_t* relative_key_name, REGSAM access);
+
+  // Closes this reg key.
+  void Close();
+
+  // Replaces the handle of the registry key and takes ownership of the handle.
+  void Set(HKEY key);
+
+  // Transfers ownership away from this object.
+  HKEY Take();
+
+  // Returns false if this key does not have the specified value, or if an error
+  // occurs while attempting to access it.
+  bool HasValue(const char16_t* value_name) const;
+
+  // Returns the number of values for this key, or 0 if the number cannot be
+  // determined.
+  DWORD GetValueCount() const;
+
+  // Determines the nth value's name.
+  LONG GetValueNameAt(int index, std::u16string* name) const;
+
+  // True while the key is valid.
+  bool Valid() const { return key_ != NULL; }
+
+  // Kills a key and everything that lives below it; please be careful when
+  // using it.
+  LONG DeleteKey(const char16_t* name);
+
+  // Deletes an empty subkey.  If the subkey has subkeys or values then this
+  // will fail.
+  LONG DeleteEmptyKey(const char16_t* name);
+
+  // Deletes a single value within the key.
+  LONG DeleteValue(const char16_t* name);
+
+  // Getters:
+
+  // Reads a REG_DWORD (uint32_t) into |out_value|. If |name| is null or empty,
+  // reads the key's default value, if any.
+  LONG ReadValueDW(const char16_t* name, DWORD* out_value) const;
+
+  // Reads a REG_QWORD (int64_t) into |out_value|. If |name| is null or empty,
+  // reads the key's default value, if any.
+  LONG ReadInt64(const char16_t* name, int64_t* out_value) const;
+
+  // Reads a string into |out_value|. If |name| is null or empty, reads
+  // the key's default value, if any.
+  LONG ReadValue(const char16_t* name, std::u16string* out_value) const;
+
+  // Reads a REG_MULTI_SZ registry field into a vector of strings. Clears
+  // |values| initially and adds further strings to the list. Returns
+  // ERROR_CANTREAD if type is not REG_MULTI_SZ.
+  LONG ReadValues(const char16_t* name, std::vector<std::u16string>* values);
+
+  // Reads raw data into |data|. If |name| is null or empty, reads the key's
+  // default value, if any.
+  LONG ReadValue(const char16_t* name,
+                 void* data,
+                 DWORD* dsize,
+                 DWORD* dtype) const;
+
+  // Setters:
+
+  // Sets an int32_t value.
+  LONG WriteValue(const char16_t* name, DWORD in_value);
+
+  // Sets a string value.
+  LONG WriteValue(const char16_t* name, const char16_t* in_value);
+
+  // Sets raw data, including type.
+  LONG WriteValue(const char16_t* name,
+                  const void* data,
+                  DWORD dsize,
+                  DWORD dtype);
+
+  HKEY Handle() const { return key_; }
+
+ private:
+  // Calls RegDeleteKeyEx on supported platforms, alternatively falls back to
+  // RegDeleteKey.
+  static LONG RegDeleteKeyExWrapper(HKEY hKey,
+                                    const char16_t* lpSubKey,
+                                    REGSAM samDesired,
+                                    DWORD Reserved);
+
+  // Recursively deletes a key and all of its subkeys.
+  static LONG RegDelRecurse(HKEY root_key,
+                            const std::u16string& name,
+                            REGSAM access);
+
+  HKEY key_;  // The registry key being iterated.
+  REGSAM wow64access_;
+
+  DISALLOW_COPY_AND_ASSIGN(RegKey);
+};
+
+// Iterates the entries found in a particular folder on the registry.
+class RegistryValueIterator {
+ public:
+  // Constructs a Registry Value Iterator with default WOW64 access.
+  RegistryValueIterator(HKEY root_key, const char16_t* folder_key);
+
+  // Constructs a Registry Key Iterator with specific WOW64 access, one of
+  // KEY_WOW64_32KEY or KEY_WOW64_64KEY, or 0.
+  // Note: |wow64access| should be the same access used to open |root_key|
+  // previously, or a predefined key (e.g. HKEY_LOCAL_MACHINE).
+  // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
+  RegistryValueIterator(HKEY root_key,
+                        const char16_t* folder_key,
+                        REGSAM wow64access);
+
+  ~RegistryValueIterator();
+
+  DWORD ValueCount() const;
+
+  // True while the iterator is valid.
+  bool Valid() const;
+
+  // Advances to the next registry entry.
+  void operator++();
+
+  const char16_t* Name() const { return name_.c_str(); }
+  const char16_t* Value() const { return value_.data(); }
+  // ValueSize() is in bytes.
+  DWORD ValueSize() const { return value_size_; }
+  DWORD Type() const { return type_; }
+
+  int Index() const { return index_; }
+
+ private:
+  // Reads in the current values.
+  bool Read();
+
+  void Initialize(HKEY root_key,
+                  const char16_t* folder_key,
+                  REGSAM wow64access);
+
+  // The registry key being iterated.
+  HKEY key_;
+
+  // Current index of the iteration.
+  int index_;
+
+  // Current values.
+  std::u16string name_;
+  std::vector<char16_t> value_;
+  DWORD value_size_;
+  DWORD type_;
+
+  DISALLOW_COPY_AND_ASSIGN(RegistryValueIterator);
+};
+
+class RegistryKeyIterator {
+ public:
+  // Constructs a Registry Key Iterator with default WOW64 access.
+  RegistryKeyIterator(HKEY root_key, const char16_t* folder_key);
+
+  // Constructs a Registry Value Iterator with specific WOW64 access, one of
+  // KEY_WOW64_32KEY or KEY_WOW64_64KEY, or 0.
+  // Note: |wow64access| should be the same access used to open |root_key|
+  // previously, or a predefined key (e.g. HKEY_LOCAL_MACHINE).
+  // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx.
+  RegistryKeyIterator(HKEY root_key,
+                      const char16_t* folder_key,
+                      REGSAM wow64access);
+
+  ~RegistryKeyIterator();
+
+  DWORD SubkeyCount() const;
+
+  // True while the iterator is valid.
+  bool Valid() const;
+
+  // Advances to the next entry in the folder.
+  void operator++();
+
+  const char16_t* Name() const { return name_; }
+
+  int Index() const { return index_; }
+
+ private:
+  // Reads in the current values.
+  bool Read();
+
+  void Initialize(HKEY root_key,
+                  const char16_t* folder_key,
+                  REGSAM wow64access);
+
+  // The registry key being iterated.
+  HKEY key_;
+
+  // Current index of the iteration.
+  int index_;
+
+  char16_t name_[MAX_PATH];
+
+  DISALLOW_COPY_AND_ASSIGN(RegistryKeyIterator);
+};
+
+}  // namespace win
+}  // namespace base
+
+#endif  // BASE_WIN_REGISTRY_H_
diff --git a/src/base/win/scoped_handle.cc b/src/base/win/scoped_handle.cc
new file mode 100644 (file)
index 0000000..cc034af
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/win/scoped_handle.h"
+#include <windows.h>
+
+namespace base {
+namespace win {
+
+// Static.
+bool HandleTraits::CloseHandle(HANDLE handle) {
+  return ::CloseHandle(handle);
+}
+
+// Static.
+void VerifierTraits::StartTracking(HANDLE handle,
+                                   const void* owner,
+                                   const void* pc1,
+                                   const void* pc2) {}
+
+// Static.
+void VerifierTraits::StopTracking(HANDLE handle,
+                                  const void* owner,
+                                  const void* pc1,
+                                  const void* pc2) {}
+
+void DisableHandleVerifier() {}
+
+void OnHandleBeingClosed(HANDLE handle) {}
+
+}  // namespace win
+}  // namespace base
diff --git a/src/base/win/scoped_handle.h b/src/base/win/scoped_handle.h
new file mode 100644 (file)
index 0000000..4573af3
--- /dev/null
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_WIN_SCOPED_HANDLE_H_
+#define BASE_WIN_SCOPED_HANDLE_H_
+
+#include <windows.h>
+
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+
+// TODO(rvargas): remove this with the rest of the verifier.
+#if defined(COMPILER_MSVC)
+#include <intrin.h>
+#define BASE_WIN_GET_CALLER _ReturnAddress()
+#elif defined(COMPILER_GCC)
+#define BASE_WIN_GET_CALLER \
+  __builtin_extract_return_addr(\ __builtin_return_address(0))
+#endif
+
+namespace base {
+namespace win {
+
+// Generic wrapper for raw handles that takes care of closing handles
+// automatically. The class interface follows the style of
+// the ScopedFILE class with two additions:
+//   - IsValid() method can tolerate multiple invalid handle values such as NULL
+//     and INVALID_HANDLE_VALUE (-1) for Win32 handles.
+//   - Set() (and the constructors and assignment operators that call it)
+//     preserve the Windows LastError code. This ensures that GetLastError() can
+//     be called after stashing a handle in a GenericScopedHandle object. Doing
+//     this explicitly is necessary because of bug 528394 and VC++ 2015.
+template <class Traits, class Verifier>
+class GenericScopedHandle {
+ public:
+  typedef typename Traits::Handle Handle;
+
+  GenericScopedHandle() : handle_(Traits::NullHandle()) {}
+
+  explicit GenericScopedHandle(Handle handle) : handle_(Traits::NullHandle()) {
+    Set(handle);
+  }
+
+  GenericScopedHandle(GenericScopedHandle&& other)
+      : handle_(Traits::NullHandle()) {
+    Set(other.Take());
+  }
+
+  ~GenericScopedHandle() { Close(); }
+
+  bool IsValid() const { return Traits::IsHandleValid(handle_); }
+
+  GenericScopedHandle& operator=(GenericScopedHandle&& other) {
+    DCHECK_NE(this, &other);
+    Set(other.Take());
+    return *this;
+  }
+
+  void Set(Handle handle) {
+    if (handle_ != handle) {
+      // Preserve old LastError to avoid bug 528394.
+      auto last_error = ::GetLastError();
+      Close();
+
+      if (Traits::IsHandleValid(handle)) {
+        handle_ = handle;
+      }
+      ::SetLastError(last_error);
+    }
+  }
+
+  Handle Get() const { return handle_; }
+
+  // Transfers ownership away from this object.
+  Handle Take() {
+    Handle temp = handle_;
+    handle_ = Traits::NullHandle();
+    return temp;
+  }
+
+  // Explicitly closes the owned handle.
+  void Close() {
+    if (Traits::IsHandleValid(handle_)) {
+      Traits::CloseHandle(handle_);
+      handle_ = Traits::NullHandle();
+    }
+  }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(ScopedHandleTest, ActiveVerifierWrongOwner);
+  FRIEND_TEST_ALL_PREFIXES(ScopedHandleTest, ActiveVerifierUntrackedHandle);
+  Handle handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(GenericScopedHandle);
+};
+
+#undef BASE_WIN_GET_CALLER
+
+// The traits class for Win32 handles that can be closed via CloseHandle() API.
+class HandleTraits {
+ public:
+  typedef HANDLE Handle;
+
+  // Closes the handle.
+  static bool CloseHandle(HANDLE handle);
+
+  // Returns true if the handle value is valid.
+  static bool IsHandleValid(HANDLE handle) {
+    return handle != NULL && handle != INVALID_HANDLE_VALUE;
+  }
+
+  // Returns NULL handle value.
+  static HANDLE NullHandle() { return NULL; }
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(HandleTraits);
+};
+
+// Do-nothing verifier.
+class DummyVerifierTraits {
+ public:
+  typedef HANDLE Handle;
+
+  static void StartTracking(HANDLE handle,
+                            const void* owner,
+                            const void* pc1,
+                            const void* pc2) {}
+  static void StopTracking(HANDLE handle,
+                           const void* owner,
+                           const void* pc1,
+                           const void* pc2) {}
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(DummyVerifierTraits);
+};
+
+// Performs actual run-time tracking.
+class VerifierTraits {
+ public:
+  typedef HANDLE Handle;
+
+  static void StartTracking(HANDLE handle,
+                            const void* owner,
+                            const void* pc1,
+                            const void* pc2);
+  static void StopTracking(HANDLE handle,
+                           const void* owner,
+                           const void* pc1,
+                           const void* pc2);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(VerifierTraits);
+};
+
+typedef GenericScopedHandle<HandleTraits, VerifierTraits> ScopedHandle;
+
+// This function may be called by the embedder to disable the use of
+// VerifierTraits at runtime. It has no effect if DummyVerifierTraits is used
+// for ScopedHandle.
+void DisableHandleVerifier();
+
+// This should be called whenever the OS is closing a handle, if extended
+// verification of improper handle closing is desired. If |handle| is being
+// tracked by the handle verifier and ScopedHandle is not the one closing it,
+// a CHECK is generated.
+void OnHandleBeingClosed(HANDLE handle);
+}  // namespace win
+}  // namespace base
+
+#endif  // BASE_WIN_SCOPED_HANDLE_H_
diff --git a/src/base/win/scoped_process_information.cc b/src/base/win/scoped_process_information.cc
new file mode 100644 (file)
index 0000000..93fb9e6
--- /dev/null
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/win/scoped_process_information.h"
+
+#include "base/logging.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+namespace win {
+
+namespace {
+
+// Duplicates source into target, returning true upon success. |target| is
+// guaranteed to be untouched in case of failure. Succeeds with no side-effects
+// if source is NULL.
+bool CheckAndDuplicateHandle(HANDLE source, ScopedHandle* target) {
+  if (!source)
+    return true;
+
+  HANDLE temp = NULL;
+  if (!::DuplicateHandle(::GetCurrentProcess(), source, ::GetCurrentProcess(),
+                         &temp, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+    DWORD last_error = ::GetLastError();
+    DPLOG(ERROR) << "Failed to duplicate a handle " << last_error;
+    ::SetLastError(last_error);
+    return false;
+  }
+  target->Set(temp);
+  return true;
+}
+
+}  // namespace
+
+ScopedProcessInformation::ScopedProcessInformation()
+    : process_id_(0), thread_id_(0) {}
+
+ScopedProcessInformation::ScopedProcessInformation(
+    const PROCESS_INFORMATION& process_info)
+    : process_id_(0), thread_id_(0) {
+  Set(process_info);
+}
+
+ScopedProcessInformation::~ScopedProcessInformation() {
+  Close();
+}
+
+bool ScopedProcessInformation::IsValid() const {
+  return process_id_ || process_handle_.Get() || thread_id_ ||
+         thread_handle_.Get();
+}
+
+void ScopedProcessInformation::Close() {
+  process_handle_.Close();
+  thread_handle_.Close();
+  process_id_ = 0;
+  thread_id_ = 0;
+}
+
+void ScopedProcessInformation::Set(const PROCESS_INFORMATION& process_info) {
+  if (IsValid())
+    Close();
+
+  process_handle_.Set(process_info.hProcess);
+  thread_handle_.Set(process_info.hThread);
+  process_id_ = process_info.dwProcessId;
+  thread_id_ = process_info.dwThreadId;
+}
+
+bool ScopedProcessInformation::DuplicateFrom(
+    const ScopedProcessInformation& other) {
+  DCHECK(!IsValid()) << "target ScopedProcessInformation must be NULL";
+  DCHECK(other.IsValid()) << "source ScopedProcessInformation must be valid";
+
+  if (CheckAndDuplicateHandle(other.process_handle(), &process_handle_) &&
+      CheckAndDuplicateHandle(other.thread_handle(), &thread_handle_)) {
+    process_id_ = other.process_id();
+    thread_id_ = other.thread_id();
+    return true;
+  }
+
+  return false;
+}
+
+PROCESS_INFORMATION ScopedProcessInformation::Take() {
+  PROCESS_INFORMATION process_information = {};
+  process_information.hProcess = process_handle_.Take();
+  process_information.hThread = thread_handle_.Take();
+  process_information.dwProcessId = process_id();
+  process_information.dwThreadId = thread_id();
+  process_id_ = 0;
+  thread_id_ = 0;
+
+  return process_information;
+}
+
+HANDLE ScopedProcessInformation::TakeProcessHandle() {
+  process_id_ = 0;
+  return process_handle_.Take();
+}
+
+HANDLE ScopedProcessInformation::TakeThreadHandle() {
+  thread_id_ = 0;
+  return thread_handle_.Take();
+}
+
+}  // namespace win
+}  // namespace base
diff --git a/src/base/win/scoped_process_information.h b/src/base/win/scoped_process_information.h
new file mode 100644 (file)
index 0000000..557555d
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_WIN_SCOPED_PROCESS_INFORMATION_H_
+#define BASE_WIN_SCOPED_PROCESS_INFORMATION_H_
+
+#include <windows.h>
+
+#include "base/macros.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+namespace win {
+
+// Manages the closing of process and thread handles from PROCESS_INFORMATION
+// structures. Allows clients to take ownership of either handle independently.
+class ScopedProcessInformation {
+ public:
+  ScopedProcessInformation();
+  explicit ScopedProcessInformation(const PROCESS_INFORMATION& process_info);
+  ~ScopedProcessInformation();
+
+  // Returns true iff this instance is holding a thread and/or process handle.
+  bool IsValid() const;
+
+  // Closes the held thread and process handles, if any.
+  void Close();
+
+  // Populates this instance with the provided |process_info|.
+  void Set(const PROCESS_INFORMATION& process_info);
+
+  // Populates this instance with duplicate handles and the thread/process IDs
+  // from |other|. Returns false in case of failure, in which case this instance
+  // will be completely unpopulated.
+  bool DuplicateFrom(const ScopedProcessInformation& other);
+
+  // Transfers ownership of the held PROCESS_INFORMATION, if any, away from this
+  // instance.
+  PROCESS_INFORMATION Take();
+
+  // Transfers ownership of the held process handle, if any, away from this
+  // instance. Note that the related process_id will also be cleared.
+  HANDLE TakeProcessHandle();
+
+  // Transfers ownership of the held thread handle, if any, away from this
+  // instance. Note that the related thread_id will also be cleared.
+  HANDLE TakeThreadHandle();
+
+  // Returns the held process handle, if any, while retaining ownership.
+  HANDLE process_handle() const { return process_handle_.Get(); }
+
+  // Returns the held thread handle, if any, while retaining ownership.
+  HANDLE thread_handle() const { return thread_handle_.Get(); }
+
+  // Returns the held process id, if any.
+  DWORD process_id() const { return process_id_; }
+
+  // Returns the held thread id, if any.
+  DWORD thread_id() const { return thread_id_; }
+
+ private:
+  ScopedHandle process_handle_;
+  ScopedHandle thread_handle_;
+  DWORD process_id_;
+  DWORD thread_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedProcessInformation);
+};
+
+}  // namespace win
+}  // namespace base
+
+#endif  // BASE_WIN_SCOPED_PROCESS_INFORMATION_H_
diff --git a/src/base/win/win_util.h b/src/base/win/win_util.h
new file mode 100644 (file)
index 0000000..cab52f7
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_WIN_WIN_UTIL_H_
+#define BASE_WIN_WIN_UTIL_H_
+
+#include <string>
+#include <string_view>
+
+namespace base {
+
+// Windows API calls take wchar_t but on that platform wchar_t should be the
+// same as a char16_t.
+inline const wchar_t* ToWCharT(const std::u16string* s) {
+  static_assert(sizeof(std::u16string::value_type) == sizeof(wchar_t));
+  return reinterpret_cast<const wchar_t*>(s->c_str());
+}
+
+inline const wchar_t* ToWCharT(const char16_t* s) {
+  return reinterpret_cast<const wchar_t*>(s);
+}
+
+inline wchar_t* ToWCharT(char16_t* s) {
+  return reinterpret_cast<wchar_t*>(s);
+}
+
+}  // namespace base
+
+#endif  // BASE_WIN_WIN_UTIL_H_
diff --git a/src/gn/action_target_generator.cc b/src/gn/action_target_generator.cc
new file mode 100644 (file)
index 0000000..73fa288
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/action_target_generator.h"
+
+#include "base/stl_util.h"
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+ActionTargetGenerator::ActionTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Target::OutputType type,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err), output_type_(type) {}
+
+ActionTargetGenerator::~ActionTargetGenerator() = default;
+
+void ActionTargetGenerator::DoRun() {
+  target_->set_output_type(output_type_);
+
+  if (!FillSources())
+    return;
+  if (output_type_ == Target::ACTION_FOREACH && target_->sources().empty()) {
+    // Foreach rules must always have some sources to have an effect.
+    *err_ =
+        Err(function_call_, "action_foreach target has no sources.",
+            "If you don't specify any sources, there is nothing to run your\n"
+            "script over.");
+    return;
+  }
+
+  if (!FillInputs())
+    return;
+
+  if (!FillScript())
+    return;
+
+  if (!FillScriptArgs())
+    return;
+
+  if (!FillResponseFileContents())
+    return;
+
+  if (!FillOutputs(output_type_ == Target::ACTION_FOREACH))
+    return;
+
+  if (!FillDepfile())
+    return;
+
+  if (!FillPool())
+    return;
+
+  if (!FillCheckIncludes())
+    return;
+
+  if (!CheckOutputs())
+    return;
+
+  // Action outputs don't depend on the current toolchain so we can skip adding
+  // that dependency.
+
+  // response_file_contents and {{response_file_name}} in the args must go
+  // together.
+  const auto& required_args_substitutions =
+      target_->action_values().args().required_types();
+  bool has_rsp_file_name = base::ContainsValue(required_args_substitutions,
+                                               &SubstitutionRspFileName);
+  if (target_->action_values().uses_rsp_file() && !has_rsp_file_name) {
+    *err_ = Err(
+        function_call_, "Missing {{response_file_name}} in args.",
+        "This target defines response_file_contents but doesn't use\n"
+        "{{response_file_name}} in the args, which means the response file\n"
+        "will be unused.");
+    return;
+  }
+  if (!target_->action_values().uses_rsp_file() && has_rsp_file_name) {
+    *err_ = Err(
+        function_call_, "Missing response_file_contents definition.",
+        "This target uses {{response_file_name}} in the args, but does not\n"
+        "define response_file_contents which means the response file\n"
+        "will be empty.");
+    return;
+  }
+}
+
+bool ActionTargetGenerator::FillScript() {
+  // If this gets called, the target type requires a script, so error out
+  // if it doesn't have one.
+  const Value* value = scope_->GetValue(variables::kScript, true);
+  if (!value) {
+    *err_ = Err(function_call_, "This target type requires a \"script\".");
+    return false;
+  }
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  SourceFile script_file = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, scope_->settings()->build_settings()->root_path_utf8());
+  if (err_->has_error())
+    return false;
+  target_->action_values().set_script(script_file);
+  return true;
+}
+
+bool ActionTargetGenerator::FillScriptArgs() {
+  const Value* value = scope_->GetValue(variables::kArgs, true);
+  if (!value)
+    return true;  // Nothing to do.
+
+  if (!target_->action_values().args().Parse(*value, err_))
+    return false;
+  if (!EnsureValidSubstitutions(
+          target_->action_values().args().required_types(),
+          &IsValidScriptArgsSubstitution, value->origin(), err_))
+    return false;
+
+  return true;
+}
+
+bool ActionTargetGenerator::FillResponseFileContents() {
+  const Value* value = scope_->GetValue(variables::kResponseFileContents, true);
+  if (!value)
+    return true;  // Nothing to do.
+
+  if (!target_->action_values().rsp_file_contents().Parse(*value, err_))
+    return false;
+  if (!EnsureValidSubstitutions(
+          target_->action_values().rsp_file_contents().required_types(),
+          &IsValidSourceSubstitution, value->origin(), err_))
+    return false;
+
+  return true;
+}
+
+bool ActionTargetGenerator::FillDepfile() {
+  const Value* value = scope_->GetValue(variables::kDepfile, true);
+  if (!value)
+    return true;
+
+  SubstitutionPattern depfile;
+  if (!depfile.Parse(*value, err_))
+    return false;
+  if (!EnsureSubstitutionIsInOutputDir(depfile, *value))
+    return false;
+
+  target_->action_values().set_depfile(depfile);
+  return true;
+}
+
+bool ActionTargetGenerator::FillPool() {
+  const Value* value = scope_->GetValue(variables::kPool, true);
+  if (!value)
+    return true;
+
+  Label label =
+      Label::Resolve(scope_->GetSourceDir(),
+                     scope_->settings()->build_settings()->root_path_utf8(),
+                     ToolchainLabelForScope(scope_), *value, err_);
+  if (err_->has_error())
+    return false;
+
+  LabelPtrPair<Pool> pair(label);
+  pair.origin = target_->defined_from();
+
+  target_->action_values().set_pool(std::move(pair));
+  return true;
+}
+
+bool ActionTargetGenerator::CheckOutputs() {
+  const SubstitutionList& outputs = target_->action_values().outputs();
+  if (outputs.list().empty()) {
+    *err_ =
+        Err(function_call_, "Action has no outputs.",
+            "If you have no outputs, the build system can not tell when your\n"
+            "script needs to be run.");
+    return false;
+  }
+
+  if (output_type_ == Target::ACTION) {
+    if (!outputs.required_types().empty()) {
+      *err_ = Err(
+          function_call_, "Action has patterns in the output.",
+          "An action target should have the outputs completely specified. If\n"
+          "you want to provide a mapping from source to output, use an\n"
+          "\"action_foreach\" target.");
+      return false;
+    }
+  } else if (output_type_ == Target::ACTION_FOREACH) {
+    // A foreach target should always have a pattern in the outputs.
+    if (outputs.required_types().empty()) {
+      *err_ = Err(
+          function_call_, "action_foreach should have a pattern in the output.",
+          "An action_foreach target should have a source expansion pattern in\n"
+          "it to map source file to unique output file name. Otherwise, the\n"
+          "build system can't determine when your script needs to be run.");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ActionTargetGenerator::FillInputs() {
+  const Value* value = scope_->GetValue(variables::kInputs, true);
+  if (!value)
+    return true;
+
+  Target::FileList dest_inputs;
+  if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+                                  scope_->GetSourceDir(), &dest_inputs, err_))
+    return false;
+  target_->config_values().inputs().swap(dest_inputs);
+  return true;
+}
diff --git a/src/gn/action_target_generator.h b/src/gn/action_target_generator.h
new file mode 100644 (file)
index 0000000..5d7f88c
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ACTION_TARGET_GENERATOR_H_
+#define TOOLS_GN_ACTION_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target.h"
+#include "gn/target_generator.h"
+
+// Populates a Target with the values from an action[_foreach] rule.
+class ActionTargetGenerator : public TargetGenerator {
+ public:
+  ActionTargetGenerator(Target* target,
+                        Scope* scope,
+                        const FunctionCallNode* function_call,
+                        Target::OutputType type,
+                        Err* err);
+  ~ActionTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillScript();
+  bool FillScriptArgs();
+  bool FillResponseFileContents();
+  bool FillDepfile();
+  bool FillPool();
+  bool FillInputs();
+
+  // Checks for errors in the outputs variable.
+  bool CheckOutputs();
+
+  Target::OutputType output_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActionTargetGenerator);
+};
+
+#endif  // TOOLS_GN_ACTION_TARGET_GENERATOR_H_
diff --git a/src/gn/action_target_generator_unittest.cc b/src/gn/action_target_generator_unittest.cc
new file mode 100644 (file)
index 0000000..08ff8af
--- /dev/null
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using ActionTargetGenerator = TestWithScheduler;
+
+// Tests that actions can't have output substitutions.
+TEST_F(ActionTargetGenerator, ActionOutputSubstitutions) {
+  TestWithScope setup;
+  Scope::ItemVector items_;
+  setup.scope()->set_item_collector(&items_);
+
+  // First test one with no substitutions, this should be valid.
+  TestParseInput input_good(
+      R"(action("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/one.txt" ]
+         })");
+  ASSERT_FALSE(input_good.has_error());
+
+  // This should run fine.
+  Err err;
+  input_good.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // Same thing with a pattern in the output should fail.
+  TestParseInput input_bad(
+      R"(action("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}.txt" ]
+         })");
+  ASSERT_FALSE(input_bad.has_error());
+
+  // This should run fine.
+  input_bad.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+}
+
+// Tests that arg and response file substitutions are validated for
+// action_foreach targets.
+TEST_F(ActionTargetGenerator, ActionForeachSubstitutions) {
+  TestWithScope setup;
+  Scope::ItemVector items_;
+  setup.scope()->set_item_collector(&items_);
+
+  // Args listing a response file but missing a response file definition should
+  // fail.
+  TestParseInput input_missing_resp_file(
+      R"(action_foreach("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}" ]
+           args = [ "{{response_file_name}}" ]
+         })");
+  ASSERT_FALSE(input_missing_resp_file.has_error());
+  Err err;
+  input_missing_resp_file.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+
+  // Adding a response file definition should pass.
+  err = Err();
+  TestParseInput input_resp_file(
+      R"(action_foreach("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}" ]
+           args = [ "{{response_file_name}}" ]
+           response_file_contents = [ "{{source_name_part}}" ]
+         })");
+  ASSERT_FALSE(input_resp_file.has_error());
+  input_resp_file.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // Defining a response file but not referencing it should fail.
+  err = Err();
+  TestParseInput input_missing_rsp_args(
+      R"(action_foreach("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}" ]
+           args = [ "{{source_name_part}}" ]
+           response_file_contents = [ "{{source_name_part}}" ]
+         })");
+  ASSERT_FALSE(input_missing_rsp_args.has_error());
+  input_missing_rsp_args.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error()) << err.message();
+
+  // Bad substitutions in args.
+  err = Err();
+  TestParseInput input_bad_args(
+      R"(action_foreach("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}" ]
+           args = [ "{{response_file_name}} {{ldflags}}" ]
+           response_file_contents = [ "{{source_name_part}}" ]
+         })");
+  ASSERT_FALSE(input_bad_args.has_error());
+  input_bad_args.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error()) << err.message();
+
+  // Bad substitutions in response file contents.
+  err = Err();
+  TestParseInput input_bad_rsp(
+      R"(action_foreach("foo") {
+           script = "//foo.py"
+           sources = [ "//bar.txt" ]
+           outputs = [ "//out/Debug/{{source_name_part}}" ]
+           args = [ "{{response_file_name}}" ]
+           response_file_contents = [ "{{source_name_part}} {{ldflags}}" ]
+         })");
+  ASSERT_FALSE(input_bad_rsp.has_error());
+  input_bad_rsp.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error()) << err.message();
+}
diff --git a/src/gn/action_values.cc b/src/gn/action_values.cc
new file mode 100644 (file)
index 0000000..56810e4
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/action_values.h"
+
+#include "gn/settings.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+ActionValues::ActionValues() = default;
+
+ActionValues::~ActionValues() = default;
+
+void ActionValues::GetOutputsAsSourceFiles(
+    const Target* target,
+    std::vector<SourceFile>* result) const {
+  if (target->output_type() == Target::BUNDLE_DATA) {
+    // The bundle_data target has no output, the real output will be generated
+    // by the create_bundle target.
+  } else if (target->output_type() == Target::COPY_FILES ||
+             target->output_type() == Target::ACTION_FOREACH) {
+    // Copy and foreach applies the outputs to the sources.
+    SubstitutionWriter::ApplyListToSources(target, target->settings(), outputs_,
+                                           target->sources(), result);
+  } else {
+    // Actions (and anything else that happens to specify an output) just use
+    // the output list with no substitution.
+    SubstitutionWriter::GetListAsSourceFiles(outputs_, result);
+  }
+}
diff --git a/src/gn/action_values.h b/src/gn/action_values.h
new file mode 100644 (file)
index 0000000..af6ab12
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ACTION_VALUES_H_
+#define TOOLS_GN_ACTION_VALUES_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/label_ptr.h"
+#include "gn/source_file.h"
+#include "gn/substitution_list.h"
+
+class Pool;
+class Target;
+
+// Holds the values (outputs, args, script name, etc.) for either an action or
+// an action_foreach target.
+class ActionValues {
+ public:
+  ActionValues();
+  ~ActionValues();
+
+  // Filename of the script to execute.
+  const SourceFile& script() const { return script_; }
+  void set_script(const SourceFile& s) { script_ = s; }
+
+  // Arguments to the script.
+  SubstitutionList& args() { return args_; }
+  const SubstitutionList& args() const { return args_; }
+
+  // Files created by the script. These are strings rather than SourceFiles
+  // since they will often contain {{source expansions}}.
+  SubstitutionList& outputs() { return outputs_; }
+  const SubstitutionList& outputs() const { return outputs_; }
+
+  // Expands the outputs() above to the final SourceFile list.
+  void GetOutputsAsSourceFiles(const Target* target,
+                               std::vector<SourceFile>* result) const;
+
+  // Depfile generated by the script.
+  const SubstitutionPattern& depfile() const { return depfile_; }
+  bool has_depfile() const { return !depfile_.ranges().empty(); }
+  void set_depfile(const SubstitutionPattern& depfile) { depfile_ = depfile; }
+
+  // Response file contents. Empty means no response file.
+  SubstitutionList& rsp_file_contents() { return rsp_file_contents_; }
+  const SubstitutionList& rsp_file_contents() const {
+    return rsp_file_contents_;
+  }
+  bool uses_rsp_file() const { return !rsp_file_contents_.list().empty(); }
+
+  // Pool option
+  const LabelPtrPair<Pool>& pool() const { return pool_; }
+  void set_pool(LabelPtrPair<Pool> pool) { pool_ = std::move(pool); }
+
+ private:
+  SourceFile script_;
+  SubstitutionList args_;
+  SubstitutionList outputs_;
+  SubstitutionPattern depfile_;
+  SubstitutionList rsp_file_contents_;
+  LabelPtrPair<Pool> pool_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActionValues);
+};
+
+#endif  // TOOLS_GN_ACTION_VALUES_H_
diff --git a/src/gn/analyzer.cc b/src/gn/analyzer.cc
new file mode 100644 (file)
index 0000000..a42779a
--- /dev/null
@@ -0,0 +1,492 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/analyzer.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "gn/builder.h"
+#include "gn/config.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/loader.h"
+#include "gn/location.h"
+#include "gn/pool.h"
+#include "gn/source_file.h"
+#include "gn/target.h"
+
+namespace {
+
+struct Inputs {
+  std::vector<SourceFile> source_vec;
+  std::vector<Label> compile_vec;
+  std::vector<Label> test_vec;
+  bool compile_included_all = false;
+  std::set<const SourceFile*> source_files;
+  std::set<Label> compile_labels;
+  std::set<Label> test_labels;
+};
+
+struct Outputs {
+  std::string status;
+  std::string error;
+  bool compile_includes_all = false;
+  std::set<Label> compile_labels;
+  std::set<Label> test_labels;
+  std::set<Label> invalid_labels;
+};
+
+std::set<Label> LabelsFor(const std::set<const Target*>& targets) {
+  std::set<Label> labels;
+  for (auto* target : targets)
+    labels.insert(target->label());
+  return labels;
+}
+
+std::set<const Target*> Intersect(const std::set<const Target*>& l,
+                                  const std::set<const Target*>& r) {
+  std::set<const Target*> result;
+  std::set_intersection(l.begin(), l.end(), r.begin(), r.end(),
+                        std::inserter(result, result.begin()));
+  return result;
+}
+
+std::vector<std::string> GetStringVector(const base::DictionaryValue& dict,
+                                         const std::string& key,
+                                         Err* err) {
+  std::vector<std::string> strings;
+  const base::ListValue* lst;
+  bool ret = dict.GetList(key, &lst);
+  if (!ret) {
+    *err = Err(Location(), "Input does not have a key named \"" + key +
+                               "\" with a list value.");
+    return strings;
+  }
+
+  for (size_t i = 0; i < lst->GetSize(); i++) {
+    std::string s;
+    ret = lst->GetString(i, &s);
+    if (!ret) {
+      *err = Err(Location(), "Item " + std::to_string(i) + " of \"" + key +
+                                 "\" is not a string.");
+      strings.clear();
+      return strings;
+    }
+    strings.push_back(std::move(s));
+  }
+  *err = Err();
+  return strings;
+}
+
+void WriteString(base::DictionaryValue& dict,
+                 const std::string& key,
+                 const std::string& value) {
+  dict.SetKey(key, base::Value(value));
+};
+
+void WriteLabels(const Label& default_toolchain,
+                 base::DictionaryValue& dict,
+                 const std::string& key,
+                 const std::set<Label>& labels) {
+  std::vector<std::string> strings;
+  auto value = std::make_unique<base::ListValue>();
+  for (const auto& l : labels)
+    strings.push_back(l.GetUserVisibleName(default_toolchain));
+  std::sort(strings.begin(), strings.end());
+  value->AppendStrings(strings);
+  dict.SetWithoutPathExpansion(key, std::move(value));
+}
+
+Label AbsoluteOrSourceAbsoluteStringToLabel(const Label& default_toolchain,
+                                            const std::string& s,
+                                            Err* err) {
+  if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) {
+    *err = Err(Location(),
+               "\"" + s + "\" is not a source-absolute or absolute path.");
+    return Label();
+  }
+  return Label::Resolve(SourceDir("//"), std::string_view(), default_toolchain,
+                        Value(nullptr, s), err);
+}
+
+Err JSONToInputs(const Label& default_toolchain,
+                 const std::string input,
+                 Inputs* inputs) {
+  int error_code_out;
+  std::string error_msg_out;
+  int error_line_out;
+  int error_column_out;
+  std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
+      input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out,
+      &error_msg_out, &error_line_out, &error_column_out);
+  if (!value)
+    return Err(Location(), "Input is not valid JSON:" + error_msg_out);
+
+  const base::DictionaryValue* dict;
+  if (!value->GetAsDictionary(&dict))
+    return Err(Location(), "Input is not a dictionary.");
+
+  Err err;
+  std::vector<std::string> strings;
+  strings = GetStringVector(*dict, "files", &err);
+  if (err.has_error())
+    return err;
+  for (auto& s : strings) {
+    if (!IsPathSourceAbsolute(s) && !IsPathAbsolute(s)) {
+      return Err(Location(),
+                 "\"" + s + "\" is not a source-absolute or absolute path.");
+    }
+    inputs->source_vec.emplace_back(std::move(s));
+  }
+
+  strings = GetStringVector(*dict, "additional_compile_targets", &err);
+  if (err.has_error())
+    return err;
+
+  inputs->compile_included_all = false;
+  for (auto& s : strings) {
+    if (s == "all") {
+      inputs->compile_included_all = true;
+    } else {
+      inputs->compile_vec.push_back(
+          AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err));
+      if (err.has_error())
+        return err;
+    }
+  }
+
+  strings = GetStringVector(*dict, "test_targets", &err);
+  if (err.has_error())
+    return err;
+  for (auto& s : strings) {
+    inputs->test_vec.push_back(
+        AbsoluteOrSourceAbsoluteStringToLabel(default_toolchain, s, &err));
+    if (err.has_error())
+      return err;
+  }
+
+  for (auto& s : inputs->source_vec)
+    inputs->source_files.insert(&s);
+  for (auto& l : inputs->compile_vec)
+    inputs->compile_labels.insert(l);
+  for (auto& l : inputs->test_vec)
+    inputs->test_labels.insert(l);
+  return Err();
+}
+
+std::string OutputsToJSON(const Outputs& outputs,
+                          const Label& default_toolchain,
+                          Err* err) {
+  std::string output;
+  auto value = std::make_unique<base::DictionaryValue>();
+
+  if (outputs.error.size()) {
+    WriteString(*value, "error", outputs.error);
+    WriteLabels(default_toolchain, *value, "invalid_targets",
+                outputs.invalid_labels);
+  } else {
+    WriteString(*value, "status", outputs.status);
+    if (outputs.compile_includes_all) {
+      auto compile_targets = std::make_unique<base::ListValue>();
+      compile_targets->AppendString("all");
+      value->SetWithoutPathExpansion("compile_targets",
+                                     std::move(compile_targets));
+    } else {
+      WriteLabels(default_toolchain, *value, "compile_targets",
+                  outputs.compile_labels);
+    }
+    WriteLabels(default_toolchain, *value, "test_targets", outputs.test_labels);
+  }
+
+  if (!base::JSONWriter::Write(*value.get(), &output))
+    *err = Err(Location(), "Failed to marshal JSON value for output");
+  return output;
+}
+
+}  // namespace
+
+Analyzer::Analyzer(const Builder& builder,
+                   const SourceFile& build_config_file,
+                   const SourceFile& dot_file,
+                   const SourceFileSet& build_args_dependency_files)
+    : all_items_(builder.GetAllResolvedItems()),
+      default_toolchain_(builder.loader()->GetDefaultToolchain()),
+      build_config_file_(build_config_file),
+      dot_file_(dot_file),
+      build_args_dependency_files_(build_args_dependency_files) {
+  for (const auto* item : all_items_) {
+    labels_to_items_[item->label()] = item;
+
+    // Fill dep_map_.
+    if (item->AsTarget()) {
+      for (const auto& dep_target_pair :
+           item->AsTarget()->GetDeps(Target::DEPS_ALL))
+        dep_map_.insert(std::make_pair(dep_target_pair.ptr, item));
+
+      for (const auto& dep_config_pair : item->AsTarget()->configs())
+        dep_map_.insert(std::make_pair(dep_config_pair.ptr, item));
+
+      dep_map_.insert(std::make_pair(item->AsTarget()->toolchain(), item));
+
+      if (item->AsTarget()->output_type() == Target::ACTION ||
+          item->AsTarget()->output_type() == Target::ACTION_FOREACH) {
+        const LabelPtrPair<Pool>& pool =
+            item->AsTarget()->action_values().pool();
+        if (pool.ptr)
+          dep_map_.insert(std::make_pair(pool.ptr, item));
+      }
+    } else if (item->AsConfig()) {
+      for (const auto& dep_config_pair : item->AsConfig()->configs())
+        dep_map_.insert(std::make_pair(dep_config_pair.ptr, item));
+    } else if (item->AsToolchain()) {
+      for (const auto& dep_pair : item->AsToolchain()->deps())
+        dep_map_.insert(std::make_pair(dep_pair.ptr, item));
+    } else {
+      DCHECK(item->AsPool());
+    }
+  }
+}
+
+Analyzer::~Analyzer() = default;
+
+std::string Analyzer::Analyze(const std::string& input, Err* err) const {
+  Inputs inputs;
+  Outputs outputs;
+
+  Err local_err = JSONToInputs(default_toolchain_, input, &inputs);
+  if (local_err.has_error()) {
+    outputs.error = local_err.message();
+    return OutputsToJSON(outputs, default_toolchain_, err);
+  }
+
+  std::set<Label> invalid_labels;
+  for (const auto& label : InvalidLabels(inputs.compile_labels))
+    invalid_labels.insert(label);
+  for (const auto& label : InvalidLabels(inputs.test_labels))
+    invalid_labels.insert(label);
+  if (!invalid_labels.empty()) {
+    outputs.error = "Invalid targets";
+    outputs.invalid_labels = invalid_labels;
+    return OutputsToJSON(outputs, default_toolchain_, err);
+  }
+
+  if (WereMainGNFilesModified(inputs.source_files)) {
+    outputs.status = "Found dependency (all)";
+    if (inputs.compile_included_all) {
+      outputs.compile_includes_all = true;
+    } else {
+      outputs.compile_labels.insert(inputs.compile_labels.begin(),
+                                    inputs.compile_labels.end());
+      outputs.compile_labels.insert(inputs.test_labels.begin(),
+                                    inputs.test_labels.end());
+    }
+    outputs.test_labels = inputs.test_labels;
+    return OutputsToJSON(outputs, default_toolchain_, err);
+  }
+
+  std::set<const Item*> affected_items =
+      GetAllAffectedItems(inputs.source_files);
+  std::set<const Target*> affected_targets;
+  for (const Item* affected_item : affected_items) {
+    if (affected_item->AsTarget())
+      affected_targets.insert(affected_item->AsTarget());
+  }
+
+  if (affected_targets.empty()) {
+    outputs.status = "No dependency";
+    return OutputsToJSON(outputs, default_toolchain_, err);
+  }
+
+  std::set<const Target*> root_targets;
+  for (const auto* item : all_items_) {
+    if (item->AsTarget() && dep_map_.find(item) == dep_map_.end())
+      root_targets.insert(item->AsTarget());
+  }
+
+  std::set<const Target*> compile_targets = TargetsFor(inputs.compile_labels);
+  if (inputs.compile_included_all) {
+    for (auto* root_target : root_targets)
+      compile_targets.insert(root_target);
+  }
+  std::set<const Target*> filtered_targets = Filter(compile_targets);
+  outputs.compile_labels =
+      LabelsFor(Intersect(filtered_targets, affected_targets));
+
+  // If every target is affected, simply compile All instead of listing all
+  // the targets to make the output easier to read.
+  if (inputs.compile_included_all &&
+      outputs.compile_labels.size() == filtered_targets.size())
+    outputs.compile_includes_all = true;
+
+  std::set<const Target*> test_targets = TargetsFor(inputs.test_labels);
+  outputs.test_labels = LabelsFor(Intersect(test_targets, affected_targets));
+
+  if (outputs.compile_labels.empty() && outputs.test_labels.empty())
+    outputs.status = "No dependency";
+  else
+    outputs.status = "Found dependency";
+  return OutputsToJSON(outputs, default_toolchain_, err);
+}
+
+std::set<const Item*> Analyzer::GetAllAffectedItems(
+    const std::set<const SourceFile*>& source_files) const {
+  std::set<const Item*> directly_affected_items;
+  for (auto* source_file : source_files)
+    AddItemsDirectlyReferringToFile(source_file, &directly_affected_items);
+
+  std::set<const Item*> all_affected_items;
+  for (auto* affected_item : directly_affected_items)
+    AddAllItemsReferringToItem(affected_item, &all_affected_items);
+
+  return all_affected_items;
+}
+
+std::set<Label> Analyzer::InvalidLabels(const std::set<Label>& labels) const {
+  std::set<Label> invalid_labels;
+  for (const Label& label : labels) {
+    if (labels_to_items_.find(label) == labels_to_items_.end())
+      invalid_labels.insert(label);
+  }
+  return invalid_labels;
+}
+
+std::set<const Target*> Analyzer::TargetsFor(
+    const std::set<Label>& labels) const {
+  std::set<const Target*> targets;
+  for (const auto& label : labels) {
+    auto it = labels_to_items_.find(label);
+    if (it != labels_to_items_.end()) {
+      DCHECK(it->second->AsTarget());
+      targets.insert(it->second->AsTarget());
+    }
+  }
+  return targets;
+}
+
+std::set<const Target*> Analyzer::Filter(
+    const std::set<const Target*>& targets) const {
+  std::set<const Target*> seen;
+  std::set<const Target*> filtered;
+  for (const auto* target : targets)
+    FilterTarget(target, &seen, &filtered);
+  return filtered;
+}
+
+void Analyzer::FilterTarget(const Target* target,
+                            std::set<const Target*>* seen,
+                            std::set<const Target*>* filtered) const {
+  if (seen->find(target) == seen->end()) {
+    seen->insert(target);
+    if (target->output_type() != Target::GROUP) {
+      filtered->insert(target);
+    } else {
+      for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
+        FilterTarget(pair.ptr, seen, filtered);
+    }
+  }
+}
+
+bool Analyzer::ItemRefersToFile(const Item* item,
+                                const SourceFile* file) const {
+  for (const auto& cur_file : item->build_dependency_files()) {
+    if (cur_file == *file)
+      return true;
+  }
+
+  if (const Config* config = item->AsConfig()) {
+    for (const auto& config_pair: config->configs()) {
+      if (ItemRefersToFile(config_pair.ptr, file))
+        return true;
+    }
+  }
+
+  if (!item->AsTarget())
+    return false;
+
+  const Target* target = item->AsTarget();
+  for (const auto& cur_file : target->sources()) {
+    if (cur_file == *file)
+      return true;
+  }
+  for (const auto& cur_file : target->public_headers()) {
+    if (cur_file == *file)
+      return true;
+  }
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    for (const auto& cur_file : iter.cur().inputs()) {
+      if (cur_file == *file)
+        return true;
+    }
+  }
+  for (const auto& cur_file : target->data()) {
+    if (cur_file == file->value())
+      return true;
+    if (cur_file.back() == '/' &&
+        base::StartsWith(file->value(), cur_file, base::CompareCase::SENSITIVE))
+      return true;
+  }
+
+  if (target->action_values().script().value() == file->value())
+    return true;
+
+  std::vector<SourceFile> outputs;
+  target->action_values().GetOutputsAsSourceFiles(target, &outputs);
+  for (const auto& cur_file : outputs) {
+    if (cur_file == *file)
+      return true;
+  }
+  return false;
+}
+
+void Analyzer::AddItemsDirectlyReferringToFile(
+    const SourceFile* file,
+    std::set<const Item*>* directly_affected_items) const {
+  for (const auto* item : all_items_) {
+    if (ItemRefersToFile(item, file))
+      directly_affected_items->insert(item);
+  }
+}
+
+void Analyzer::AddAllItemsReferringToItem(
+    const Item* item,
+    std::set<const Item*>* all_affected_items) const {
+  if (all_affected_items->find(item) != all_affected_items->end())
+    return;  // Already found this item.
+
+  all_affected_items->insert(item);
+
+  auto dep_begin = dep_map_.lower_bound(item);
+  auto dep_end = dep_map_.upper_bound(item);
+  for (auto cur_dep = dep_begin; cur_dep != dep_end; ++cur_dep)
+    AddAllItemsReferringToItem(cur_dep->second, all_affected_items);
+}
+
+bool Analyzer::WereMainGNFilesModified(
+    const std::set<const SourceFile*>& modified_files) const {
+  for (const auto* file : modified_files) {
+    if (*file == dot_file_)
+      return true;
+
+    if (*file == build_config_file_)
+      return true;
+
+    for (const auto& build_args_dependency_file :
+         build_args_dependency_files_) {
+      if (*file == build_args_dependency_file)
+        return true;
+    }
+  }
+
+  return false;
+}
diff --git a/src/gn/analyzer.h b/src/gn/analyzer.h
new file mode 100644 (file)
index 0000000..2ffdb52
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ANALYZER_H_
+#define TOOLS_GN_ANALYZER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "gn/builder.h"
+#include "gn/item.h"
+#include "gn/label.h"
+#include "gn/source_file.h"
+
+// An Analyzer can answer questions about a build graph. It is used
+// to answer queries for the `refs` and `analyze` commands, where we
+// need to look at the graph in ways that can't easily be determined
+// from just a single Target.
+class Analyzer {
+ public:
+  Analyzer(const Builder& builder,
+           const SourceFile& build_config_file,
+           const SourceFile& dot_file,
+           const SourceFileSet& build_args_dependency_files);
+  ~Analyzer();
+
+  // Figures out from a Buider and a JSON-formatted string containing lists
+  // of files and targets, which targets would be affected by modifications
+  // to the files . See the help text for the analyze command (kAnalyze_Help)
+  // for the specification of the input and output string formats and the
+  // expected behavior of the method.
+  std::string Analyze(const std::string& input, Err* err) const;
+
+ private:
+  // Returns the set of all items that might be affected, directly or
+  // indirectly, by modifications to the given source files.
+  std::set<const Item*> GetAllAffectedItems(
+      const std::set<const SourceFile*>& source_files) const;
+
+  // Returns the set of labels that do not refer to objects in the graph.
+  std::set<Label> InvalidLabels(const std::set<Label>& labels) const;
+
+  // Returns the set of all targets that have a label in the given set.
+  // Invalid (or missing) labels will be ignored.
+  std::set<const Target*> TargetsFor(const std::set<Label>& labels) const;
+
+  // Returns a filtered set of the given targets, meaning that for each of the
+  // given targets,
+  // - if the target is not a group, add it to the set
+  // - if the target is a group, recursively filter each dependency and add
+  //   its filtered results to the set.
+  //
+  // For example, if we had:
+  //
+  //   group("foobar") { deps = [ ":foo", ":bar" ] }
+  //   group("bar") { deps = [ ":baz", ":quux" ] }
+  //   executable("foo") { ... }
+  //   executable("baz") { ... }
+  //   executable("quux") { ... }
+  //
+  // Then the filtered version of {"foobar"} would be {":foo", ":baz",
+  // ":quux"}. This is used by the analyze command in order to only build
+  // the affected dependencies of a group (and not also build the unaffected
+  // ones).
+  //
+  // This filtering behavior is also known as "pruning" the list of targets.
+  std::set<const Target*> Filter(const std::set<const Target*>& targets) const;
+
+  // Filter an individual target and adds the results to filtered
+  // (see Filter(), above).
+  void FilterTarget(const Target*,
+                    std::set<const Target*>* seen,
+                    std::set<const Target*>* filtered) const;
+
+  bool ItemRefersToFile(const Item* item, const SourceFile* file) const;
+
+  void AddItemsDirectlyReferringToFile(
+      const SourceFile* file,
+      std::set<const Item*>* affected_items) const;
+
+  void AddAllItemsReferringToItem(const Item* item,
+                                  std::set<const Item*>* affected_items) const;
+
+  // Main GN files stand for files whose context are used globally to execute
+  // every other build files, this list includes dot file, build config file,
+  // build args files etc.
+  bool WereMainGNFilesModified(
+      const std::set<const SourceFile*>& modified_files) const;
+
+  std::vector<const Item*> all_items_;
+  std::map<Label, const Item*> labels_to_items_;
+  Label default_toolchain_;
+
+  // Maps items to the list of items that depend on them.
+  std::multimap<const Item*, const Item*> dep_map_;
+
+  const SourceFile build_config_file_;
+  const SourceFile dot_file_;
+  const SourceFileSet build_args_dependency_files_;
+};
+
+#endif  // TOOLS_GN_ANALYZER_H_
diff --git a/src/gn/analyzer_unittest.cc b/src/gn/analyzer_unittest.cc
new file mode 100644 (file)
index 0000000..558700e
--- /dev/null
@@ -0,0 +1,755 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/analyzer.h"
+
+#include "gn/c_tool.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/config.h"
+#include "gn/general_tool.h"
+#include "gn/loader.h"
+#include "gn/pool.h"
+#include "gn/settings.h"
+#include "gn/source_file.h"
+#include "gn/substitution_list.h"
+#include "gn/target.h"
+#include "gn/tool.h"
+#include "gn/toolchain.h"
+#include "util/test/test.h"
+
+namespace gn_analyzer_unittest {
+
+class MockLoader : public Loader {
+ public:
+  MockLoader() = default;
+
+  void Load(const SourceFile& file,
+            const LocationRange& origin,
+            const Label& toolchain_name) override {}
+  void ToolchainLoaded(const Toolchain* toolchain) override {}
+  Label GetDefaultToolchain() const override {
+    return Label(SourceDir("//tc/"), "default");
+  }
+  const Settings* GetToolchainSettings(const Label& label) const override {
+    return nullptr;
+  }
+  SourceFile BuildFileForLabel(const Label& label) const override {
+    return SourceFile(label.dir().value() + "BUILD.gn");
+  }
+
+ private:
+  ~MockLoader() override = default;
+};
+
+class AnalyzerTest : public testing::Test {
+ public:
+  AnalyzerTest()
+      : loader_(new MockLoader),
+        builder_(loader_.get()),
+        settings_(&build_settings_, std::string()),
+        other_settings_(&build_settings_, std::string()) {
+    build_settings_.SetBuildDir(SourceDir("//out/"));
+
+    settings_.set_toolchain_label(Label(SourceDir("//tc/"), "default"));
+    settings_.set_default_toolchain_label(settings_.toolchain_label());
+    tc_dir_ = settings_.toolchain_label().dir();
+    tc_name_ = settings_.toolchain_label().name();
+
+    other_settings_.set_toolchain_label(Label(SourceDir("//other/"), "tc"));
+    other_settings_.set_default_toolchain_label(
+        other_settings_.toolchain_label());
+    tc_other_dir_ = other_settings_.toolchain_label().dir();
+    tc_other_name_ = other_settings_.toolchain_label().name();
+  }
+
+  std::unique_ptr<Target> MakeTarget(const std::string& dir,
+                                     const std::string& name) {
+    Label label(SourceDir(dir), name, tc_dir_, tc_name_);
+    return std::make_unique<Target>(&settings_, label);
+  }
+
+  std::unique_ptr<Target> MakeTargetOtherToolchain(const std::string& dir,
+                                                   const std::string& name) {
+    Label label(SourceDir(dir), name, tc_other_dir_, tc_other_name_);
+    return std::make_unique<Target>(&other_settings_, label);
+  }
+
+  std::unique_ptr<Config> MakeConfig(const std::string& dir,
+                                     const std::string& name) {
+    Label label(SourceDir(dir), name, tc_dir_, tc_name_);
+    return std::make_unique<Config>(&settings_, label);
+  }
+
+  std::unique_ptr<Pool> MakePool(const std::string& dir,
+                                 const std::string& name) {
+    Label label(SourceDir(dir), name, tc_dir_, tc_name_);
+    return std::make_unique<Pool>(&settings_, label);
+  }
+
+  void RunAnalyzerTest(const std::string& input,
+                       const std::string& expected_output) {
+    Analyzer analyzer(builder_, SourceFile("//build/config/BUILDCONFIG.gn"),
+                      SourceFile("//.gn"),
+                      {SourceFile("//out/debug/args.gn"),
+                       SourceFile("//build/default_args.gn")});
+    Err err;
+    std::string actual_output = analyzer.Analyze(input, &err);
+    EXPECT_EQ(err.has_error(), false);
+    EXPECT_EQ(expected_output, actual_output);
+  }
+
+ protected:
+  scoped_refptr<MockLoader> loader_;
+  Builder builder_;
+  BuildSettings build_settings_;
+
+  Settings settings_;
+  SourceDir tc_dir_;
+  std::string tc_name_;
+
+  Settings other_settings_;
+  SourceDir tc_other_dir_;
+  std::string tc_other_name_;
+};
+
+// Tests that a target is marked as affected if its sources are modified.
+TEST_F(AnalyzerTest, TargetRefersToSources) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->sources().push_back(SourceFile("//dir/file_name.cc"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if its public headers are modified.
+TEST_F(AnalyzerTest, TargetRefersToPublicHeaders) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/header_name.h" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->public_headers().push_back(SourceFile("//dir/header_name.h"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/header_name.h" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if its inputs are modified.
+TEST_F(AnalyzerTest, TargetRefersToInputs) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/extra_input.cc" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  SourceFile extra_input(SourceFile("//dir/extra_input.cc"));
+  t_raw->config_values().inputs().push_back(extra_input);
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/extra_input.cc" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+
+  t_raw->config_values().inputs().clear();
+  std::unique_ptr<Config> c = MakeConfig("//dir", "config_name");
+  c->own_values().inputs().push_back(extra_input);
+  t_raw->configs().push_back(LabelConfigPair(c.get()));
+  builder_.ItemDefined(std::move(c));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/extra_input.cc" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if a sub-config is modified.
+//
+// This test uses two levels of sub-configs to ensure the config hierarchy
+// is completely traversed.
+TEST_F(AnalyzerTest, SubConfigIsModified) {
+  std::unique_ptr<Config> ssc = MakeConfig("//dir3", "subsubconfig_name");
+  ssc->build_dependency_files().insert(SourceFile("//dir3/BUILD.gn"));
+
+  std::unique_ptr<Config> sc = MakeConfig("//dir2", "subconfig_name");
+  sc->configs().push_back(LabelConfigPair(ssc->label()));
+
+  std::unique_ptr<Config> c = MakeConfig("//dir", "config_name");
+  c->configs().push_back(LabelConfigPair(sc->label()));
+
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  t->configs().push_back(LabelConfigPair(c.get()));
+
+  builder_.ItemDefined(std::move(ssc));
+  builder_.ItemDefined(std::move(sc));
+  builder_.ItemDefined(std::move(c));
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir3/BUILD.gn" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if its data are modified.
+TEST_F(AnalyzerTest, TargetRefersToData) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/data.html" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->data().push_back("//dir/data.html");
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/data.html" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if the target is an action and its
+// action script is modified.
+TEST_F(AnalyzerTest, TargetRefersToActionScript) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  t->set_output_type(Target::ACTION);
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/script.py" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->action_values().set_script(SourceFile("//dir/script.py"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/script.py" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that a target is marked as affected if its build dependency files are
+// modified.
+TEST_F(AnalyzerTest, TargetRefersToBuildDependencyFiles) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that if a target is marked as affected, then it propagates to dependent
+// test_targets.
+TEST_F(AnalyzerTest, AffectedTargetpropagatesToDependentTargets) {
+  std::unique_ptr<Target> t1 = MakeTarget("//dir", "target_name1");
+  std::unique_ptr<Target> t2 = MakeTarget("//dir", "target_name2");
+  std::unique_ptr<Target> t3 = MakeTarget("//dir", "target_name3");
+  t1->private_deps().push_back(LabelTargetPair(t2.get()));
+  t2->private_deps().push_back(LabelTargetPair(t3.get()));
+  builder_.ItemDefined(std::move(t1));
+  builder_.ItemDefined(std::move(t2));
+
+  Target* t3_raw = t3.get();
+  builder_.ItemDefined(std::move(t3));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name1", "//dir:target_name2" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t3_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name1", "//dir:target_name2" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name1","//dir:target_name2"])"
+      "}");
+}
+
+// Tests that if a config is marked as affected, then it propagates to dependent
+// test_targets.
+TEST_F(AnalyzerTest, AffectedConfigpropagatesToDependentTargets) {
+  std::unique_ptr<Config> c = MakeConfig("//dir", "config_name");
+  Config* c_raw = c.get();
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  t->configs().push_back(LabelConfigPair(c.get()));
+  builder_.ItemDefined(std::move(t));
+  builder_.ItemDefined(std::move(c));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  c_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that if toolchain is marked as affected, then it propagates to
+// dependent test_targets.
+TEST_F(AnalyzerTest, AffectedToolchainpropagatesToDependentTargets) {
+  std::unique_ptr<Target> target = MakeTarget("//dir", "target_name");
+  target->set_output_type(Target::EXECUTABLE);
+  Toolchain* toolchain = new Toolchain(&settings_, settings_.toolchain_label());
+
+  // The tool is not used anywhere, but is required to construct the dependency
+  // between a target and the toolchain.
+  std::unique_ptr<Tool> fake_tool = Tool::CreateTool(CTool::kCToolLink);
+  fake_tool->set_outputs(
+      SubstitutionList::MakeForTest("//out/debug/output.txt"));
+  toolchain->SetTool(std::move(fake_tool));
+  builder_.ItemDefined(std::move(target));
+  builder_.ItemDefined(std::unique_ptr<Item>(toolchain));
+
+  RunAnalyzerTest(
+      R"({
+         "files": [ "//tc/BUILD.gn" ],
+         "additional_compile_targets": [ "all" ],
+         "test_targets": [ "//dir:target_name" ]
+         })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  toolchain->build_dependency_files().insert(SourceFile("//tc/BUILD.gn"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//tc/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that if a pool is marked as affected, then it propagates to dependent
+// targets.
+TEST_F(AnalyzerTest, AffectedPoolpropagatesToDependentTargets) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  t->set_output_type(Target::ACTION);
+  std::unique_ptr<Pool> p = MakePool("//dir", "pool_name");
+  Pool* p_raw = p.get();
+  t->action_values().set_pool(LabelPtrPair<Pool>(p.get()));
+
+  builder_.ItemDefined(std::move(t));
+  builder_.ItemDefined(std::move(p));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  p_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": [ "//dir:target_name" ]
+       })",
+      "{"
+      R"("compile_targets":["all"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that when dependency was found, the "compile_targets" in the output is
+// not "all".
+TEST_F(AnalyzerTest, CompileTargetsAllWasPruned) {
+  std::unique_ptr<Target> t1 = MakeTarget("//dir", "target_name1");
+  std::unique_ptr<Target> t2 = MakeTarget("//dir", "target_name2");
+  t2->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
+  builder_.ItemDefined(std::move(t1));
+  builder_.ItemDefined(std::move(t2));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": []
+       })",
+      "{"
+      R"("compile_targets":["//dir:target_name2"],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":[])"
+      "}");
+}
+
+// Tests that output is "No dependency" when no dependency is found.
+TEST_F(AnalyzerTest, NoDependency) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  builder_.ItemDefined(std::move(t));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//dir/BUILD.gn" ],
+       "additional_compile_targets": [ "all" ],
+       "test_targets": []
+       })",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+}
+
+// Tests that output is "No dependency" when no files or targets are provided.
+TEST_F(AnalyzerTest, NoFilesNoTargets) {
+  RunAnalyzerTest(
+      R"({
+       "files": [],
+       "additional_compile_targets": [],
+       "test_targets": []
+      })",
+      "{"
+      R"("compile_targets":[],)"
+      R"("status":"No dependency",)"
+      R"("test_targets":[])"
+      "}");
+}
+
+// Tests that output displays proper error message when given files aren't
+// source-absolute or absolute path.
+TEST_F(AnalyzerTest, FilesArentSourceAbsolute) {
+  RunAnalyzerTest(
+      R"({
+       "files": [ "a.cc" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+      })",
+      "{"
+      R"("error":)"
+      R"("\"a.cc\" is not a source-absolute or absolute path.",)"
+      R"("invalid_targets":[])"
+      "}");
+}
+
+// Tests that output displays proper error message when input is ill-formed.
+TEST_F(AnalyzerTest, WrongInputFields) {
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//a.cc" ],
+       "compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+      })",
+      "{"
+      R"("error":)"
+      R"("Input does not have a key named )"
+      R"(\"additional_compile_targets\" with a list value.",)"
+      R"("invalid_targets":[])"
+      "}");
+}
+
+// Bails out early with "Found dependency (all)" if dot file is modified.
+TEST_F(AnalyzerTest, DotFileWasModified) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  builder_.ItemDefined(std::move(t));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//.gn" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+      })",
+      "{"
+      R"("compile_targets":["//dir:target_name"],)"
+      R"/("status":"Found dependency (all)",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Bails out early with "Found dependency (all)" if master build config file is
+// modified.
+TEST_F(AnalyzerTest, BuildConfigFileWasModified) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  builder_.ItemDefined(std::move(t));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//build/config/BUILDCONFIG.gn" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+      })",
+      "{"
+      R"("compile_targets":["//dir:target_name"],)"
+      R"/("status":"Found dependency (all)",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Bails out early with "Found dependency (all)" if a build args dependency file
+// is modified.
+TEST_F(AnalyzerTest, BuildArgsDependencyFileWasModified) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  builder_.ItemDefined(std::move(t));
+
+  RunAnalyzerTest(
+      R"({
+       "files": [ "//build/default_args.gn" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name" ]
+      })",
+      "{"
+      R"("compile_targets":["//dir:target_name"],)"
+      R"/("status":"Found dependency (all)",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+}
+
+// Tests that targets in explicitly labelled with the default toolchain are
+// included when their sources change.
+// change.
+TEST_F(AnalyzerTest, TargetToolchainSpecifiedRefersToSources) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  Target* t_raw = t.get();
+  builder_.ItemDefined(std::move(t));
+
+  RunAnalyzerTest(
+      R"/({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": ["all"],
+       "test_targets": [ "//dir:target_name(//tc:default)" ]
+       })/",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->sources().push_back(SourceFile("//dir/file_name.cc"));
+
+  RunAnalyzerTest(
+      R"*({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name(//tc:default)" ]
+       })*",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"Found dependency",)/"
+      R"/("test_targets":["//dir:target_name"])/"
+      "}");
+}
+
+// Tests that targets in alternate toolchains are affected when their sources
+// change.
+TEST_F(AnalyzerTest, TargetAlternateToolchainRefersToSources) {
+  std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
+  std::unique_ptr<Target> t_alt =
+      MakeTargetOtherToolchain("//dir", "target_name");
+  Target* t_raw = t.get();
+  Target* t_alt_raw = t_alt.get();
+  builder_.ItemDefined(std::move(t));
+  builder_.ItemDefined(std::move(t_alt));
+
+  RunAnalyzerTest(
+      R"/({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": ["all"],
+       "test_targets": [ "//dir:target_name", "//dir:target_name(//other:tc)" ]
+       })/",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"No dependency",)/"
+      R"("test_targets":[])"
+      "}");
+
+  t_raw->sources().push_back(SourceFile("//dir/file_name.cc"));
+  t_alt_raw->sources().push_back(SourceFile("//dir/alt_file_name.cc"));
+
+  RunAnalyzerTest(
+      R"*({
+       "files": [ "//dir/file_name.cc" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name", "//dir:target_name(//other:tc)" ]
+       })*",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"Found dependency",)/"
+      R"("test_targets":["//dir:target_name"])"
+      "}");
+
+  RunAnalyzerTest(
+      R"*({
+       "files": [ "//dir/alt_file_name.cc" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name", "//dir:target_name(//other:tc)" ]
+       })*",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"Found dependency",)/"
+      R"/("test_targets":["//dir:target_name(//other:tc)"])/"
+      "}");
+
+  RunAnalyzerTest(
+      R"*({
+       "files": [ "//dir/file_name.cc", "//dir/alt_file_name.cc" ],
+       "additional_compile_targets": [],
+       "test_targets": [ "//dir:target_name", "//dir:target_name(//other:tc)" ]
+       })*",
+      "{"
+      R"("compile_targets":[],)"
+      R"/("status":"Found dependency",)/"
+      R"/("test_targets":["//dir:target_name","//dir:target_name(//other:tc)"])/"
+      "}");
+}
+
+}  // namespace gn_analyzer_unittest
diff --git a/src/gn/args.cc b/src/gn/args.cc
new file mode 100644 (file)
index 0000000..c21beea
--- /dev/null
@@ -0,0 +1,437 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/args.h"
+
+#include "gn/settings.h"
+#include "gn/source_file.h"
+#include "gn/string_utils.h"
+#include "gn/variables.h"
+#include "util/build_config.h"
+#include "util/sys_info.h"
+
+const char kBuildArgs_Help[] =
+    R"(Build Arguments Overview
+
+  Build arguments are variables passed in from outside of the build that build
+  files can query to determine how the build works.
+
+How build arguments are set
+
+  First, system default arguments are set based on the current system. The
+  built-in arguments are:
+   - host_cpu
+   - host_os
+   - current_cpu
+   - current_os
+   - target_cpu
+   - target_os
+
+  Next, project-specific overrides are applied. These are specified inside
+  the default_args variable of //.gn. See "gn help dotfile" for more.
+
+  If specified, arguments from the --args command line flag are used. If that
+  flag is not specified, args from previous builds in the build directory will
+  be used (this is in the file args.gn in the build directory).
+
+  Last, for targets being compiled with a non-default toolchain, the toolchain
+  overrides are applied. These are specified in the toolchain_args section of a
+  toolchain definition. The use-case for this is that a toolchain may be
+  building code for a different platform, and that it may want to always
+  specify Posix, for example. See "gn help toolchain" for more.
+
+  If you specify an override for a build argument that never appears in a
+  "declare_args" call, a nonfatal error will be displayed.
+
+Examples
+
+  gn args out/FooBar
+      Create the directory out/FooBar and open an editor. You would type
+      something like this into that file:
+          enable_doom_melon=false
+          os="android"
+
+  gn gen out/FooBar --args="enable_doom_melon=true os=\"android\""
+      This will overwrite the build directory with the given arguments. (Note
+      that the quotes inside the args command will usually need to be escaped
+      for your shell to pass through strings values.)
+
+How build arguments are used
+
+  If you want to use an argument, you use declare_args() and specify default
+  values. These default values will apply if none of the steps listed in the
+  "How build arguments are set" section above apply to the given argument, but
+  the defaults will not override any of these.
+
+  Often, the root build config file will declare global arguments that will be
+  passed to all buildfiles. Individual build files can also specify arguments
+  that apply only to those files. It is also useful to specify build args in an
+  "import"-ed file if you want such arguments to apply to multiple buildfiles.
+)";
+
+namespace {
+
+// Removes all entries in |overrides| that are in |declared_overrides|.
+void RemoveDeclaredOverrides(const Scope::KeyValueMap& declared_arguments,
+                             Scope::KeyValueMap* overrides) {
+  for (Scope::KeyValueMap::iterator override = overrides->begin();
+       override != overrides->end();) {
+    if (declared_arguments.find(override->first) == declared_arguments.end())
+      ++override;
+    else
+      overrides->erase(override++);
+  }
+}
+
+}  // namespace
+
+Args::ValueWithOverride::ValueWithOverride()
+    : default_value(), has_override(false), override_value() {}
+
+Args::ValueWithOverride::ValueWithOverride(const Value& def_val)
+    : default_value(def_val), has_override(false), override_value() {}
+
+Args::ValueWithOverride::~ValueWithOverride() = default;
+
+Args::Args() = default;
+
+Args::Args(const Args& other)
+    : overrides_(other.overrides_),
+      all_overrides_(other.all_overrides_),
+      declared_arguments_per_toolchain_(
+          other.declared_arguments_per_toolchain_),
+      toolchain_overrides_(other.toolchain_overrides_) {}
+
+Args::~Args() = default;
+
+void Args::AddArgOverride(const char* name, const Value& value) {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  overrides_[std::string_view(name)] = value;
+  all_overrides_[std::string_view(name)] = value;
+}
+
+void Args::AddArgOverrides(const Scope::KeyValueMap& overrides) {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  for (const auto& cur_override : overrides) {
+    overrides_[cur_override.first] = cur_override.second;
+    all_overrides_[cur_override.first] = cur_override.second;
+  }
+}
+
+void Args::AddDefaultArgOverrides(const Scope::KeyValueMap& overrides) {
+  std::lock_guard<std::mutex> lock(lock_);
+  for (const auto& cur_override : overrides)
+    overrides_[cur_override.first] = cur_override.second;
+}
+
+const Value* Args::GetArgOverride(const char* name) const {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  Scope::KeyValueMap::const_iterator found =
+      all_overrides_.find(std::string_view(name));
+  if (found == all_overrides_.end())
+    return nullptr;
+  return &found->second;
+}
+
+void Args::SetupRootScope(Scope* dest,
+                          const Scope::KeyValueMap& toolchain_overrides) const {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  SetSystemVarsLocked(dest);
+
+  // Apply overrides for already declared args.
+  // (i.e. the system vars we set above)
+  ApplyOverridesLocked(overrides_, dest);
+  ApplyOverridesLocked(toolchain_overrides, dest);
+
+  OverridesForToolchainLocked(dest) = toolchain_overrides;
+
+  SaveOverrideRecordLocked(toolchain_overrides);
+}
+
+bool Args::DeclareArgs(const Scope::KeyValueMap& args,
+                       Scope* scope_to_set,
+                       Err* err) const {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  Scope::KeyValueMap& declared_arguments(
+      DeclaredArgumentsForToolchainLocked(scope_to_set));
+
+  const Scope::KeyValueMap& toolchain_overrides(
+      OverridesForToolchainLocked(scope_to_set));
+
+  for (const auto& arg : args) {
+    // Verify that the value hasn't already been declared. We want each value
+    // to be declared only once.
+    //
+    // The tricky part is that a buildfile can be interpreted multiple times
+    // when used from different toolchains, so we can't just check that we've
+    // seen it before. Instead, we check that the location matches.
+    Scope::KeyValueMap::iterator previously_declared =
+        declared_arguments.find(arg.first);
+    if (previously_declared != declared_arguments.end()) {
+      if (previously_declared->second.origin() != arg.second.origin()) {
+        // Declaration location mismatch.
+        *err = Err(
+            arg.second.origin(), "Duplicate build argument declaration.",
+            "Here you're declaring an argument that was already declared "
+            "elsewhere.\nYou can only declare each argument once in the entire "
+            "build so there is one\ncanonical place for documentation and the "
+            "default value. Either move this\nargument to the build config "
+            "file (for visibility everywhere) or to a .gni file\nthat you "
+            "\"import\" from the files where you need it (preferred).");
+        err->AppendSubErr(Err(previously_declared->second.origin(),
+                              "Previous declaration.",
+                              "See also \"gn help buildargs\" for more on how "
+                              "build arguments work."));
+        return false;
+      }
+    } else {
+      declared_arguments.insert(arg);
+    }
+
+    // In all the cases below, mark the variable used. If a variable is set
+    // that's only used in one toolchain, we don't want to report unused
+    // variable errors in other toolchains. Also, in some cases it's reasonable
+    // for the build file to overwrite the value with a different value based
+    // on some other condition without dereferencing the value first.
+
+    // Check whether this argument has been overridden on the toolchain level
+    // and use the override instead.
+    Scope::KeyValueMap::const_iterator toolchain_override =
+        toolchain_overrides.find(arg.first);
+    if (toolchain_override != toolchain_overrides.end()) {
+      scope_to_set->SetValue(toolchain_override->first,
+                             toolchain_override->second,
+                             toolchain_override->second.origin());
+      scope_to_set->MarkUsed(arg.first);
+      continue;
+    }
+
+    // Check whether this argument has been overridden and use the override
+    // instead.
+    Scope::KeyValueMap::const_iterator override = overrides_.find(arg.first);
+    if (override != overrides_.end()) {
+      scope_to_set->SetValue(override->first, override->second,
+                             override->second.origin());
+      scope_to_set->MarkUsed(override->first);
+      continue;
+    }
+
+    scope_to_set->SetValue(arg.first, arg.second, arg.second.origin());
+    scope_to_set->MarkUsed(arg.first);
+  }
+
+  return true;
+}
+
+bool Args::VerifyAllOverridesUsed(Err* err) const {
+  std::lock_guard<std::mutex> lock(lock_);
+  Scope::KeyValueMap unused_overrides(all_overrides_);
+  for (const auto& map_pair : declared_arguments_per_toolchain_)
+    RemoveDeclaredOverrides(map_pair.second, &unused_overrides);
+
+  if (unused_overrides.empty())
+    return true;
+
+  // Some assignments in args.gn had no effect.  Show an error for the first
+  // unused assignment.
+  std::string_view name = unused_overrides.begin()->first;
+  const Value& value = unused_overrides.begin()->second;
+
+  std::string err_help(
+      "The variable \"" + name +
+      "\" was set as a build argument\n"
+      "but never appeared in a declare_args() block in any buildfile.\n\n"
+      "To view all possible args, run \"gn args --list <out_dir>\"");
+
+  // Use all declare_args for a spelling suggestion.
+  std::vector<std::string_view> candidates;
+  for (const auto& map_pair : declared_arguments_per_toolchain_) {
+    for (const auto& declared_arg : map_pair.second)
+      candidates.push_back(declared_arg.first);
+  }
+  std::string_view suggestion = SpellcheckString(name, candidates);
+  if (!suggestion.empty())
+    err_help = "Did you mean \"" + suggestion + "\"?\n\n" + err_help;
+
+  *err = Err(value.origin(), "Build argument has no effect.", err_help);
+  return false;
+}
+
+Args::ValueWithOverrideMap Args::GetAllArguments() const {
+  ValueWithOverrideMap result;
+
+  std::lock_guard<std::mutex> lock(lock_);
+
+  // Sort the keys from declared_arguments_per_toolchain_ so
+  // the return value will be deterministic.
+  std::vector<const Settings*> keys;
+  keys.reserve(declared_arguments_per_toolchain_.size());
+  for (const auto& map_pair : declared_arguments_per_toolchain_) {
+    keys.push_back(map_pair.first);
+  }
+  std::sort(keys.begin(), keys.end(),
+            [](const Settings* a, const Settings* b) -> bool {
+              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)
+      result.insert(std::make_pair(arg.first, ValueWithOverride(arg.second)));
+  }
+
+  // Merge in overrides.
+  for (const auto& over : overrides_) {
+    auto found = result.find(over.first);
+    if (found != result.end()) {
+      found->second.has_override = true;
+      found->second.override_value = over.second;
+    }
+  }
+
+  return result;
+}
+
+void Args::SetSystemVarsLocked(Scope* dest) const {
+  // Host OS.
+  const char* os = nullptr;
+#if defined(OS_WIN) || defined(OS_MSYS)
+  os = "win";
+#elif defined(OS_MACOSX)
+  os = "mac";
+#elif defined(OS_LINUX)
+  os = "linux";
+#elif defined(OS_FREEBSD)
+  os = "freebsd";
+#elif defined(OS_AIX)
+  os = "aix";
+#elif defined(OS_OPENBSD)
+  os = "openbsd";
+#elif defined(OS_HAIKU)
+  os = "haiku";
+#elif defined(OS_SOLARIS)
+  os = "solaris";
+#elif defined(OS_NETBSD)
+  os = "netbsd";
+#else
+#error Unknown OS type.
+#endif
+  // NOTE: Adding a new port? Please follow
+  // https://chromium.googlesource.com/chromium/src/+/master/docs/new_port_policy.md
+
+  // Host architecture.
+  static const char kX86[] = "x86";
+  static const char kX64[] = "x64";
+  static const char kArm[] = "arm";
+  static const char kArm64[] = "arm64";
+  static const char kMips[] = "mipsel";
+  static const char kMips64[] = "mips64el";
+  static const char kS390X[] = "s390x";
+  static const char kPPC64[] = "ppc64";
+  static const char kRISCV32[] = "riscv32";
+  static const char kRISCV64[] = "riscv64";
+  static const char kE2K[] = "e2k";
+  const char* arch = nullptr;
+
+  // Set the host CPU architecture based on the underlying OS, not
+  // whatever the current bit-tedness of the GN binary is.
+  std::string os_arch = OperatingSystemArchitecture();
+  if (os_arch == "x86" || os_arch == "BePC")
+    arch = kX86;
+  else if (os_arch == "x86_64")
+    arch = kX64;
+  else if (os_arch == "aarch64" || os_arch == "arm64")
+    arch = kArm64;
+  else if (os_arch.substr(0, 3) == "arm")
+    arch = kArm;
+  else if (os_arch == "mips")
+    arch = kMips;
+  else if (os_arch == "mips64")
+    arch = kMips64;
+  else if (os_arch == "s390x")
+    arch = kS390X;
+  else if (os_arch == "ppc64" || os_arch == "ppc64le")
+    // We handle the endianness inside //build/config/host_byteorder.gni.
+    // This allows us to use the same toolchain as ppc64 BE
+    // and specific flags are included using the host_byteorder logic.
+    arch = kPPC64;
+  else if (os_arch == "riscv32")
+    arch = kRISCV32;
+  else if (os_arch == "riscv64")
+    arch = kRISCV64;
+  else if (os_arch == "e2k")
+    arch = kE2K;
+  else
+    CHECK(false) << "OS architecture not handled. (" << os_arch << ")";
+
+  // Save the OS and architecture as build arguments that are implicitly
+  // declared. This is so they can be overridden in a toolchain build args
+  // override, and so that they will appear in the "gn args" output.
+  Value empty_string(nullptr, std::string());
+
+  Value os_val(nullptr, std::string(os));
+  dest->SetValue(variables::kHostOs, os_val, nullptr);
+  dest->SetValue(variables::kTargetOs, empty_string, nullptr);
+  dest->SetValue(variables::kCurrentOs, empty_string, nullptr);
+
+  Value arch_val(nullptr, std::string(arch));
+  dest->SetValue(variables::kHostCpu, arch_val, nullptr);
+  dest->SetValue(variables::kTargetCpu, empty_string, nullptr);
+  dest->SetValue(variables::kCurrentCpu, empty_string, nullptr);
+
+  Scope::KeyValueMap& declared_arguments(
+      DeclaredArgumentsForToolchainLocked(dest));
+  declared_arguments[variables::kHostOs] = os_val;
+  declared_arguments[variables::kCurrentOs] = empty_string;
+  declared_arguments[variables::kTargetOs] = empty_string;
+  declared_arguments[variables::kHostCpu] = arch_val;
+  declared_arguments[variables::kCurrentCpu] = empty_string;
+  declared_arguments[variables::kTargetCpu] = empty_string;
+
+  // Mark these variables used so the build config file can override them
+  // without getting a warning about overwriting an unused variable.
+  dest->MarkUsed(variables::kHostCpu);
+  dest->MarkUsed(variables::kCurrentCpu);
+  dest->MarkUsed(variables::kTargetCpu);
+  dest->MarkUsed(variables::kHostOs);
+  dest->MarkUsed(variables::kCurrentOs);
+  dest->MarkUsed(variables::kTargetOs);
+}
+
+void Args::ApplyOverridesLocked(const Scope::KeyValueMap& values,
+                                Scope* scope) const {
+  const Scope::KeyValueMap& declared_arguments(
+      DeclaredArgumentsForToolchainLocked(scope));
+
+  // Only set a value if it has been declared.
+  for (const auto& val : values) {
+    Scope::KeyValueMap::const_iterator declared =
+        declared_arguments.find(val.first);
+
+    if (declared == declared_arguments.end())
+      continue;
+
+    scope->SetValue(val.first, val.second, val.second.origin());
+  }
+}
+
+void Args::SaveOverrideRecordLocked(const Scope::KeyValueMap& values) const {
+  for (const auto& val : values)
+    all_overrides_[val.first] = val.second;
+}
+
+Scope::KeyValueMap& Args::DeclaredArgumentsForToolchainLocked(
+    Scope* scope) const {
+  return declared_arguments_per_toolchain_[scope->settings()];
+}
+
+Scope::KeyValueMap& Args::OverridesForToolchainLocked(Scope* scope) const {
+  return toolchain_overrides_[scope->settings()];
+}
diff --git a/src/gn/args.h b/src/gn/args.h
new file mode 100644 (file)
index 0000000..992d48f
--- /dev/null
@@ -0,0 +1,147 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ARGS_H_
+#define TOOLS_GN_ARGS_H_
+
+#include <map>
+#include <mutex>
+#include <set>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "gn/scope.h"
+
+class Err;
+class SourceFile;
+
+extern const char kBuildArgs_Help[];
+
+// Manages build arguments. It stores the global arguments specified on the
+// command line, and sets up the root scope with the proper values.
+//
+// This class tracks accesses so we can report errors about unused variables.
+// The use case is if the user specifies an override on the command line, but
+// no buildfile actually uses that variable. We want to be able to report that
+// the argument was unused.
+class Args {
+ public:
+  struct ValueWithOverride {
+    ValueWithOverride();
+    ValueWithOverride(const Value& def_val);
+    ~ValueWithOverride();
+
+    Value default_value;  // Default value given in declare_args.
+
+    bool has_override;     // True indicates override_value is valid.
+    Value override_value;  // From .gn or the current build's "gn args".
+  };
+  using ValueWithOverrideMap = std::map<std::string_view, ValueWithOverride>;
+
+  Args();
+  Args(const Args& other);
+  ~Args();
+
+  // Specifies overrides of the build arguments. These are normally specified
+  // on the command line.
+  void AddArgOverride(const char* name, const Value& value);
+  void AddArgOverrides(const Scope::KeyValueMap& overrides);
+
+  // Specifies default overrides of the build arguments. These are normally
+  // specified in the .gn file.
+  void AddDefaultArgOverrides(const Scope::KeyValueMap& overrides);
+
+  // Returns the value corresponding to the given argument name, or NULL if no
+  // argument is set.
+  const Value* GetArgOverride(const char* name) const;
+
+  // Sets up the root scope for a toolchain. This applies the default system
+  // flags and saves the toolchain overrides so they can be applied to
+  // declare_args blocks that appear when loading files in that toolchain.
+  void SetupRootScope(Scope* dest,
+                      const Scope::KeyValueMap& toolchain_overrides) const;
+
+  // Sets up the given scope with arguments passed in.
+  //
+  // If the values specified in the args are not already set, the values in
+  // the args list will be used (which are assumed to be the defaults), but
+  // they will not override the system defaults or the current overrides.
+  //
+  // All args specified in the input will be marked as "used".
+  //
+  // On failure, the err will be set and it will return false.
+  bool DeclareArgs(const Scope::KeyValueMap& args,
+                   Scope* scope_to_set,
+                   Err* err) const;
+
+  // Checks to see if any of the overrides ever used were never declared as
+  // arguments. If there are, this returns false and sets the error.
+  bool VerifyAllOverridesUsed(Err* err) const;
+
+  // Returns information about all arguments, both defaults and overrides.
+  // This is used for the help system which is not performance critical. Use a
+  // map instead of a hash map so the arguments are sorted alphabetically.
+  ValueWithOverrideMap GetAllArguments() const;
+
+  // Returns the set of build files that may affect the build arguments, please
+  // refer to Scope for how this is determined.
+  const SourceFileSet& build_args_dependency_files() const {
+    return build_args_dependency_files_;
+  }
+
+  void set_build_args_dependency_files(
+      const SourceFileSet& build_args_dependency_files) {
+    build_args_dependency_files_ = build_args_dependency_files;
+  }
+
+ private:
+  using ArgumentsPerToolchain =
+      std::unordered_map<const Settings*, Scope::KeyValueMap>;
+
+  // Sets the default config based on the current system.
+  void SetSystemVarsLocked(Scope* scope) const;
+
+  // Sets the given already declared vars on the given scope.
+  void ApplyOverridesLocked(const Scope::KeyValueMap& values,
+                            Scope* scope) const;
+
+  void SaveOverrideRecordLocked(const Scope::KeyValueMap& values) const;
+
+  // Returns the KeyValueMap used for arguments declared for the specified
+  // toolchain.
+  Scope::KeyValueMap& DeclaredArgumentsForToolchainLocked(Scope* scope) const;
+
+  // Returns the KeyValueMap used for overrides for the specified
+  // toolchain.
+  Scope::KeyValueMap& OverridesForToolchainLocked(Scope* scope) const;
+
+  // Since this is called during setup which we assume is single-threaded,
+  // this is not protected by the lock. It should be set only during init.
+  Scope::KeyValueMap overrides_;
+
+  mutable std::mutex lock_;
+
+  // Maintains a list of all overrides we've ever seen. This is the main
+  // |overrides_| as well as toolchain overrides. Tracking this allows us to
+  // check for overrides that were specified but never used.
+  mutable Scope::KeyValueMap all_overrides_;
+
+  // Maps from Settings (which corresponds to a toolchain) to the map of
+  // declared variables. This is used to tracks all variables declared in any
+  // buildfile. This is so we can see if the user set variables on the command
+  // line that are not used anywhere. Each map is toolchain specific as each
+  // toolchain may define variables in different locations.
+  mutable ArgumentsPerToolchain declared_arguments_per_toolchain_;
+
+  // Overrides for individual toolchains. This is necessary so we
+  // can apply the correct override for the current toolchain, once
+  // we see an argument declaration.
+  mutable ArgumentsPerToolchain toolchain_overrides_;
+
+  SourceFileSet build_args_dependency_files_;
+
+  DISALLOW_ASSIGN(Args);
+};
+
+#endif  // TOOLS_GN_ARGS_H_
diff --git a/src/gn/args_unittest.cc b/src/gn/args_unittest.cc
new file mode 100644 (file)
index 0000000..650db6d
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/args.h"
+
+#include "gn/scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+// Assertions for VerifyAllOverridesUsed() and DeclareArgs() with multiple
+// toolchains.
+TEST(ArgsTest, VerifyAllOverridesUsed) {
+  TestWithScope setup1, setup2;
+  Args args;
+  Scope::KeyValueMap key_value_map1;
+  Err err;
+  LiteralNode assignment1;
+
+  setup1.scope()->SetValue("a", Value(nullptr, true), &assignment1);
+  setup1.scope()->GetCurrentScopeValues(&key_value_map1);
+  EXPECT_TRUE(args.DeclareArgs(key_value_map1, setup1.scope(), &err));
+
+  LiteralNode assignment2;
+  setup2.scope()->SetValue("b", Value(nullptr, true), &assignment2);
+  Scope::KeyValueMap key_value_map2;
+  setup2.scope()->GetCurrentScopeValues(&key_value_map2);
+  EXPECT_TRUE(args.DeclareArgs(key_value_map2, setup2.scope(), &err));
+
+  // Override "a", shouldn't see any errors as "a" was defined.
+  args.AddArgOverride("a", Value(nullptr, true));
+  EXPECT_TRUE(args.VerifyAllOverridesUsed(&err));
+
+  // Override "a", & "b", shouldn't see any errors as both were defined.
+  args.AddArgOverride("b", Value(nullptr, true));
+  EXPECT_TRUE(args.VerifyAllOverridesUsed(&err));
+
+  // Override "a", "b" and "c", should fail as "c" was not defined.
+  args.AddArgOverride("c", Value(nullptr, true));
+  EXPECT_FALSE(args.VerifyAllOverridesUsed(&err));
+}
+
+// Ensure that arg overrides get only set after the they were declared.
+TEST(ArgsTest, VerifyOverrideScope) {
+  TestWithScope setup;
+  Args args;
+  Err err;
+
+  args.AddArgOverride("a", Value(nullptr, "avalue"));
+  args.AddArgOverride("current_os", Value(nullptr, "theiros"));
+
+  Scope::KeyValueMap toolchain_overrides;
+  toolchain_overrides["b"] = Value(nullptr, "bvalue");
+  toolchain_overrides["current_os"] = Value(nullptr, "myos");
+  args.SetupRootScope(setup.scope(), toolchain_overrides);
+
+  // Overrides of arguments not yet declared aren't applied yet.
+  EXPECT_EQ(nullptr, setup.scope()->GetValue("a"));
+  EXPECT_EQ(nullptr, setup.scope()->GetValue("b"));
+
+  // |current_os| is a system var. and already declared.
+  // Thus it should have our override value.
+  ASSERT_NE(nullptr, setup.scope()->GetValue("current_os"));
+  EXPECT_EQ(Value(nullptr, "myos"), *setup.scope()->GetValue("current_os"));
+
+  Scope::KeyValueMap key_value_map1;
+  key_value_map1["a"] = Value(nullptr, "avalue2");
+  key_value_map1["b"] = Value(nullptr, "bvalue2");
+  key_value_map1["c"] = Value(nullptr, "cvalue2");
+  EXPECT_TRUE(args.DeclareArgs(key_value_map1, setup.scope(), &err));
+
+  ASSERT_NE(nullptr, setup.scope()->GetValue("a"));
+  EXPECT_EQ(Value(nullptr, "avalue"), *setup.scope()->GetValue("a"));
+
+  ASSERT_NE(nullptr, setup.scope()->GetValue("b"));
+  EXPECT_EQ(Value(nullptr, "bvalue"), *setup.scope()->GetValue("b"));
+
+  // This wasn't overwritten, so it should have the default value.
+  ASSERT_NE(nullptr, setup.scope()->GetValue("c"));
+  EXPECT_EQ(Value(nullptr, "cvalue2"), *setup.scope()->GetValue("c"));
+}
diff --git a/src/gn/binary_target_generator.cc b/src/gn/binary_target_generator.cc
new file mode 100644 (file)
index 0000000..485884e
--- /dev/null
@@ -0,0 +1,248 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/binary_target_generator.h"
+
+#include "gn/config_values_generator.h"
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/rust_values_generator.h"
+#include "gn/rust_variables.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/swift_values_generator.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+BinaryTargetGenerator::BinaryTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Target::OutputType type,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err), output_type_(type) {}
+
+BinaryTargetGenerator::~BinaryTargetGenerator() = default;
+
+void BinaryTargetGenerator::DoRun() {
+  target_->set_output_type(output_type_);
+
+  if (!FillOutputName())
+    return;
+
+  if (!FillOutputPrefixOverride())
+    return;
+
+  if (!FillOutputDir())
+    return;
+
+  if (!FillOutputExtension())
+    return;
+
+  if (!FillSources())
+    return;
+
+  if (!FillPublic())
+    return;
+
+  if (!FillFriends())
+    return;
+
+  if (!FillCheckIncludes())
+    return;
+
+  if (!FillConfigs())
+    return;
+
+  if (!FillAllowCircularIncludesFrom())
+    return;
+
+  if (!FillCompleteStaticLib())
+    return;
+
+  if (!ValidateSources())
+    return;
+
+  if (target_->source_types_used().RustSourceUsed()) {
+    RustValuesGenerator rustgen(target_, scope_, function_call_, err_);
+    rustgen.Run();
+    if (err_->has_error())
+      return;
+  }
+
+  if (target_->source_types_used().SwiftSourceUsed()) {
+    SwiftValuesGenerator swiftgen(target_, scope_, err_);
+    swiftgen.Run();
+    if (err_->has_error())
+      return;
+  }
+
+  // Config values (compiler flags, etc.) set directly on this target.
+  ConfigValuesGenerator gen(&target_->config_values(), scope_,
+                            scope_->GetSourceDir(), err_);
+  gen.Run();
+  if (err_->has_error())
+    return;
+}
+
+bool BinaryTargetGenerator::FillSources() {
+  bool ret = TargetGenerator::FillSources();
+  for (std::size_t i = 0; i < target_->sources().size(); ++i) {
+    const auto& source = target_->sources()[i];
+    switch (source.type()) {
+      case SourceFile::SOURCE_CPP:
+      case SourceFile::SOURCE_MODULEMAP:
+      case SourceFile::SOURCE_H:
+      case SourceFile::SOURCE_C:
+      case SourceFile::SOURCE_M:
+      case SourceFile::SOURCE_MM:
+      case SourceFile::SOURCE_S:
+      case SourceFile::SOURCE_ASM:
+      case SourceFile::SOURCE_O:
+      case SourceFile::SOURCE_DEF:
+      case SourceFile::SOURCE_GO:
+      case SourceFile::SOURCE_RS:
+      case SourceFile::SOURCE_RC:
+      case SourceFile::SOURCE_SWIFT:
+        // These are allowed.
+        break;
+      case SourceFile::SOURCE_UNKNOWN:
+      case SourceFile::SOURCE_SWIFTMODULE:
+      case SourceFile::SOURCE_NUMTYPES:
+        *err_ =
+            Err(scope_->GetValue(variables::kSources, true)->list_value()[i],
+                std::string("Only source, header, and object files belong in "
+                            "the sources of a ") +
+                    Target::GetStringForOutputType(target_->output_type()) +
+                    ". " + source.value() + " is not one of the valid types.");
+    }
+
+    target_->source_types_used().Set(source.type());
+  }
+  return ret;
+}
+
+bool BinaryTargetGenerator::FillCompleteStaticLib() {
+  if (target_->output_type() == Target::STATIC_LIBRARY) {
+    const Value* value = scope_->GetValue(variables::kCompleteStaticLib, true);
+    if (!value)
+      return true;
+    if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+      return false;
+    target_->set_complete_static_lib(value->boolean_value());
+  }
+  return true;
+}
+
+bool BinaryTargetGenerator::FillFriends() {
+  const Value* value = scope_->GetValue(variables::kFriend, true);
+  if (value) {
+    return ExtractListOfLabelPatterns(scope_->settings()->build_settings(),
+                                      *value, scope_->GetSourceDir(),
+                                      &target_->friends(), err_);
+  }
+  return true;
+}
+
+bool BinaryTargetGenerator::FillOutputName() {
+  const Value* value = scope_->GetValue(variables::kOutputName, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+  target_->set_output_name(value->string_value());
+  return true;
+}
+
+bool BinaryTargetGenerator::FillOutputPrefixOverride() {
+  const Value* value = scope_->GetValue(variables::kOutputPrefixOverride, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+    return false;
+  target_->set_output_prefix_override(value->boolean_value());
+  return true;
+}
+
+bool BinaryTargetGenerator::FillOutputDir() {
+  const Value* value = scope_->GetValue(variables::kOutputDir, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  if (value->string_value().empty())
+    return true;  // Treat empty string as the default and do nothing.
+
+  const BuildSettings* build_settings = scope_->settings()->build_settings();
+  SourceDir dir = scope_->GetSourceDir().ResolveRelativeDir(
+      *value, err_, build_settings->root_path_utf8());
+  if (err_->has_error())
+    return false;
+
+  if (!EnsureStringIsInOutputDir(build_settings->build_dir(), dir.value(),
+                                 value->origin(), err_))
+    return false;
+  target_->set_output_dir(dir);
+  return true;
+}
+
+bool BinaryTargetGenerator::FillAllowCircularIncludesFrom() {
+  const Value* value =
+      scope_->GetValue(variables::kAllowCircularIncludesFrom, true);
+  if (!value)
+    return true;
+
+  UniqueVector<Label> circular;
+  ExtractListOfUniqueLabels(scope_->settings()->build_settings(), *value,
+                            scope_->GetSourceDir(),
+                            ToolchainLabelForScope(scope_), &circular, err_);
+  if (err_->has_error())
+    return false;
+
+  // Validate that all circular includes entries are in the deps.
+  for (const auto& cur : circular) {
+    bool found_dep = false;
+    for (const auto& dep_pair : target_->GetDeps(Target::DEPS_LINKED)) {
+      if (dep_pair.label == cur) {
+        found_dep = true;
+        break;
+      }
+    }
+    if (!found_dep) {
+      *err_ = Err(*value, "Label not in deps.",
+                  "The label \"" + cur.GetUserVisibleName(false) +
+                      "\"\nwas not in the deps of this target. "
+                      "allow_circular_includes_from only allows\ntargets "
+                      "present in the "
+                      "deps.");
+      return false;
+    }
+  }
+
+  // Add to the set.
+  for (const auto& cur : circular)
+    target_->allow_circular_includes_from().insert(cur);
+  return true;
+}
+
+bool BinaryTargetGenerator::ValidateSources() {
+  // For Rust targets, if the only source file is the root `sources` can be
+  // omitted/empty.
+  if (scope_->GetValue(variables::kRustCrateRoot, false)) {
+    target_->source_types_used().Set(SourceFile::SOURCE_RS);
+  }
+
+  if (target_->source_types_used().MixedSourceUsed()) {
+    *err_ =
+        Err(function_call_, "More than one language used in target sources.",
+            "Mixed sources are not allowed, unless they are "
+            "compilation-compatible (e.g. Objective C and C++).");
+    return false;
+  }
+  return true;
+}
diff --git a/src/gn/binary_target_generator.h b/src/gn/binary_target_generator.h
new file mode 100644 (file)
index 0000000..27dcb7a
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BINARY_TARGET_GENERATOR_H_
+#define TOOLS_GN_BINARY_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target.h"
+#include "gn/target_generator.h"
+
+// Populates a Target with the values from a binary rule (executable, shared
+// library, or static library).
+class BinaryTargetGenerator : public TargetGenerator {
+ public:
+  BinaryTargetGenerator(Target* target,
+                        Scope* scope,
+                        const FunctionCallNode* function_call,
+                        Target::OutputType type,
+                        Err* err);
+  ~BinaryTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+  bool FillSources() override;
+
+ private:
+  bool FillCompleteStaticLib();
+  bool FillFriends();
+  bool FillOutputName();
+  bool FillOutputPrefixOverride();
+  bool FillOutputDir();
+  bool FillAllowCircularIncludesFrom();
+  bool ValidateSources();
+
+  Target::OutputType output_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(BinaryTargetGenerator);
+};
+
+#endif  // TOOLS_GN_BINARY_TARGET_GENERATOR_H_
diff --git a/src/gn/build_settings.cc b/src/gn/build_settings.cc
new file mode 100644 (file)
index 0000000..22c1145
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/build_settings.h"
+
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "gn/filesystem_utils.h"
+
+BuildSettings::BuildSettings() = default;
+
+BuildSettings::BuildSettings(const BuildSettings& other)
+    : dotfile_name_(other.dotfile_name_),
+      root_path_(other.root_path_),
+      root_path_utf8_(other.root_path_utf8_),
+      secondary_source_path_(other.secondary_source_path_),
+      python_path_(other.python_path_),
+      ninja_required_version_(other.ninja_required_version_),
+      build_config_file_(other.build_config_file_),
+      arg_file_template_path_(other.arg_file_template_path_),
+      build_dir_(other.build_dir_),
+      build_args_(other.build_args_) {}
+
+void BuildSettings::SetRootTargetLabel(const Label& r) {
+  root_target_label_ = r;
+}
+
+void BuildSettings::SetRootPath(const base::FilePath& r) {
+  DCHECK(r.value()[r.value().size() - 1] != base::FilePath::kSeparators[0]);
+  root_path_ = r.NormalizePathSeparatorsTo('/');
+  root_path_utf8_ = FilePathToUTF8(root_path_);
+}
+
+void BuildSettings::SetSecondarySourcePath(const SourceDir& d) {
+  secondary_source_path_ = GetFullPath(d).NormalizePathSeparatorsTo('/');
+}
+
+void BuildSettings::SetBuildDir(const SourceDir& d) {
+  build_dir_ = d;
+}
+
+base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const {
+  return file.Resolve(root_path_).NormalizePathSeparatorsTo('/');
+}
+
+base::FilePath BuildSettings::GetFullPath(const SourceDir& dir) const {
+  return dir.Resolve(root_path_).NormalizePathSeparatorsTo('/');
+}
+
+base::FilePath BuildSettings::GetFullPath(const std::string& path,
+                                          bool as_file) const {
+  return ResolvePath(path, as_file, root_path_).NormalizePathSeparatorsTo('/');
+}
+
+base::FilePath BuildSettings::GetFullPathSecondary(
+    const SourceFile& file) const {
+  return file.Resolve(secondary_source_path_).NormalizePathSeparatorsTo('/');
+}
+
+base::FilePath BuildSettings::GetFullPathSecondary(const SourceDir& dir) const {
+  return dir.Resolve(secondary_source_path_).NormalizePathSeparatorsTo('/');
+}
+
+base::FilePath BuildSettings::GetFullPathSecondary(const std::string& path,
+                                                   bool as_file) const {
+  return ResolvePath(path, as_file, secondary_source_path_)
+      .NormalizePathSeparatorsTo('/');
+}
+
+void BuildSettings::ItemDefined(std::unique_ptr<Item> item) const {
+  DCHECK(item);
+  if (item_defined_callback_)
+    item_defined_callback_(std::move(item));
+}
diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h
new file mode 100644 (file)
index 0000000..f8dc502
--- /dev/null
@@ -0,0 +1,152 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUILD_SETTINGS_H_
+#define TOOLS_GN_BUILD_SETTINGS_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gn/args.h"
+#include "gn/label.h"
+#include "gn/scope.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/version.h"
+
+class Item;
+
+// Settings for one build, which is one toplevel output directory. There
+// may be multiple Settings objects that refer to this, one for each toolchain.
+class BuildSettings {
+ public:
+  using ItemDefinedCallback = std::function<void(std::unique_ptr<Item>)>;
+  using PrintCallback = std::function<void(const std::string&)>;
+
+  BuildSettings();
+  BuildSettings(const BuildSettings& other);
+
+  // Root target label.
+  const Label& root_target_label() const { return root_target_label_; }
+  void SetRootTargetLabel(const Label& r);
+
+  // Absolute path of the source root on the local system. Everything is
+  // relative to this. Does not end in a [back]slash.
+  const base::FilePath& root_path() const { return root_path_; }
+  const base::FilePath& dotfile_name() const { return dotfile_name_; }
+  const std::string& root_path_utf8() const { return root_path_utf8_; }
+  void SetRootPath(const base::FilePath& r);
+  void set_dotfile_name(const base::FilePath& d) { dotfile_name_ = d; }
+
+  // When nonempty, specifies a parallel directory higherarchy in which to
+  // search for buildfiles if they're not found in the root higherarchy. This
+  // allows us to keep buildfiles in a separate tree during development.
+  const base::FilePath& secondary_source_path() const {
+    return secondary_source_path_;
+  }
+  void SetSecondarySourcePath(const SourceDir& d);
+
+  // Path of the python executable to run scripts with.
+  base::FilePath python_path() const { return python_path_; }
+  void set_python_path(const base::FilePath& p) { python_path_ = p; }
+
+  // Required Ninja version.
+  const Version& ninja_required_version() const {
+    return ninja_required_version_;
+  }
+  void set_ninja_required_version(Version v) { ninja_required_version_ = v; }
+
+  const SourceFile& build_config_file() const { return build_config_file_; }
+  void set_build_config_file(const SourceFile& f) { build_config_file_ = f; }
+
+  // Path to a file containing the default text to use when running `gn args`.
+  const SourceFile& arg_file_template_path() const {
+    return arg_file_template_path_;
+  }
+  void set_arg_file_template_path(const SourceFile& f) {
+    arg_file_template_path_ = f;
+  }
+
+  // The build directory is the root of all output files. The default toolchain
+  // files go into here, and non-default toolchains will have separate
+  // toolchain-specific root directories inside this.
+  const SourceDir& build_dir() const { return build_dir_; }
+  void SetBuildDir(const SourceDir& dir);
+
+  // The build args are normally specified on the command-line.
+  Args& build_args() { return build_args_; }
+  const Args& build_args() const { return build_args_; }
+
+  // Returns the full absolute OS path cooresponding to the given file in the
+  // root source tree.
+  base::FilePath GetFullPath(const SourceFile& file) const;
+  base::FilePath GetFullPath(const SourceDir& dir) const;
+  // Works the same way as other GetFullPath.
+  // Parameter as_file defines whether path should be treated as a
+  // SourceFile or SourceDir value.
+  base::FilePath GetFullPath(const std::string& path, bool as_file) const;
+
+  // Returns the absolute OS path inside the secondary source path. Will return
+  // an empty FilePath if the secondary source path is empty. When loading a
+  // buildfile, the GetFullPath should always be consulted first.
+  base::FilePath GetFullPathSecondary(const SourceFile& file) const;
+  base::FilePath GetFullPathSecondary(const SourceDir& dir) const;
+  // Works the same way as other GetFullPathSecondary.
+  // Parameter as_file defines whether path should be treated as a
+  // SourceFile or SourceDir value.
+  base::FilePath GetFullPathSecondary(const std::string& path,
+                                      bool as_file) const;
+
+  // Called when an item is defined from a background thread.
+  void ItemDefined(std::unique_ptr<Item> item) const;
+  void set_item_defined_callback(ItemDefinedCallback cb) {
+    item_defined_callback_ = cb;
+  }
+
+  // Defines a callback that will be used to override the behavior of the
+  // print function. This is used in tests to collect print output. If the
+  // callback is is_null() (the default) the output will be printed to the
+  // console.
+  const PrintCallback& print_callback() const { return print_callback_; }
+  void set_print_callback(const PrintCallback& cb) { print_callback_ = cb; }
+
+  // A list of files that can call exec_script(). If the returned pointer is
+  // null, exec_script may be called from anywhere.
+  const SourceFileSet* exec_script_whitelist() const {
+    return exec_script_whitelist_.get();
+  }
+  void set_exec_script_whitelist(std::unique_ptr<SourceFileSet> list) {
+    exec_script_whitelist_ = std::move(list);
+  }
+
+ private:
+  Label root_target_label_;
+  base::FilePath dotfile_name_;
+  base::FilePath root_path_;
+  std::string root_path_utf8_;
+  base::FilePath secondary_source_path_;
+  base::FilePath python_path_;
+
+  // See 40045b9 for the reason behind using 1.7.2 as the default version.
+  Version ninja_required_version_{1, 7, 2};
+
+  SourceFile build_config_file_;
+  SourceFile arg_file_template_path_;
+  SourceDir build_dir_;
+  Args build_args_;
+
+  ItemDefinedCallback item_defined_callback_;
+  PrintCallback print_callback_;
+
+  std::unique_ptr<SourceFileSet> exec_script_whitelist_;
+
+  DISALLOW_ASSIGN(BuildSettings);
+};
+
+#endif  // TOOLS_GN_BUILD_SETTINGS_H_
diff --git a/src/gn/builder.cc b/src/gn/builder.cc
new file mode 100644 (file)
index 0000000..d31eaa4
--- /dev/null
@@ -0,0 +1,602 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/builder.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "gn/action_values.h"
+#include "gn/config.h"
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/loader.h"
+#include "gn/pool.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+
+namespace {
+
+using BuilderRecordSet = BuilderRecord::BuilderRecordSet;
+
+// Recursively looks in the tree for a given node, returning true if it
+// was found in the dependency graph. This is used to see if a given node
+// participates in a cycle.
+//
+// If this returns true, the cycle will be in *path. This should point to an
+// empty vector for the first call. During computation, the path will contain
+// the full dependency path to the current node.
+//
+// Return false means no cycle was found.
+bool RecursiveFindCycle(const BuilderRecord* search_in,
+                        std::vector<const BuilderRecord*>* path) {
+  path->push_back(search_in);
+  for (auto* cur : search_in->unresolved_deps()) {
+    std::vector<const BuilderRecord*>::iterator found =
+        std::find(path->begin(), path->end(), cur);
+    if (found != path->end()) {
+      // This item is already in the set, we found the cycle. Everything before
+      // the first definition of cur is irrelevant to the cycle.
+      path->erase(path->begin(), found);
+      path->push_back(cur);
+      return true;
+    }
+
+    if (RecursiveFindCycle(cur, path))
+      return true;  // Found cycle.
+  }
+  path->pop_back();
+  return false;
+}
+
+}  // namespace
+
+Builder::Builder(Loader* loader) : loader_(loader) {}
+
+Builder::~Builder() = default;
+
+void Builder::ItemDefined(std::unique_ptr<Item> item) {
+  ScopedTrace trace(TraceItem::TRACE_DEFINE_TARGET, item->label());
+  trace.SetToolchain(item->settings()->toolchain_label());
+
+  BuilderRecord::ItemType type = BuilderRecord::TypeOfItem(item.get());
+
+  Err err;
+  BuilderRecord* record =
+      GetOrCreateRecordOfType(item->label(), item->defined_from(), type, &err);
+  if (!record) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+
+  // Check that it's not been already defined.
+  if (record->item()) {
+    err = Err(item->defined_from(), "Duplicate definition.",
+              "The item\n  " + item->label().GetUserVisibleName(false) +
+                  "\nwas already defined.");
+    err.AppendSubErr(
+        Err(record->item()->defined_from(), "Previous definition:"));
+    g_scheduler->FailWithError(err);
+    return;
+  }
+
+  record->set_item(std::move(item));
+
+  // Do target-specific dependency setup. This will also schedule dependency
+  // loads for targets that are required.
+  switch (type) {
+    case BuilderRecord::ITEM_TARGET:
+      TargetDefined(record, &err);
+      break;
+    case BuilderRecord::ITEM_CONFIG:
+      ConfigDefined(record, &err);
+      break;
+    case BuilderRecord::ITEM_TOOLCHAIN:
+      ToolchainDefined(record, &err);
+      break;
+    default:
+      break;
+  }
+  if (err.has_error()) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+
+  if (record->can_resolve()) {
+    if (!ResolveItem(record, &err)) {
+      g_scheduler->FailWithError(err);
+      return;
+    }
+  }
+}
+
+const Item* Builder::GetItem(const Label& label) const {
+  const BuilderRecord* record = GetRecord(label);
+  if (!record)
+    return nullptr;
+  return record->item();
+}
+
+const Toolchain* Builder::GetToolchain(const Label& label) const {
+  const BuilderRecord* record = GetRecord(label);
+  if (!record)
+    return nullptr;
+  if (!record->item())
+    return nullptr;
+  return record->item()->AsToolchain();
+}
+
+std::vector<const BuilderRecord*> Builder::GetAllRecords() const {
+  std::vector<const BuilderRecord*> result;
+  result.reserve(records_.size());
+  for (const auto& record : records_)
+    result.push_back(record.second.get());
+  return result;
+}
+
+std::vector<const Item*> Builder::GetAllResolvedItems() const {
+  std::vector<const Item*> result;
+  result.reserve(records_.size());
+  for (const auto& record : records_) {
+    if (record.second->type() != BuilderRecord::ITEM_UNKNOWN &&
+        record.second->should_generate() && record.second->item()) {
+      result.push_back(record.second->item());
+    }
+  }
+
+  return result;
+}
+
+std::vector<const Target*> Builder::GetAllResolvedTargets() const {
+  std::vector<const Target*> result;
+  result.reserve(records_.size());
+  for (const auto& record : records_) {
+    if (record.second->type() == BuilderRecord::ITEM_TARGET &&
+        record.second->should_generate() && record.second->item())
+      result.push_back(record.second->item()->AsTarget());
+  }
+  return result;
+}
+
+const BuilderRecord* Builder::GetRecord(const Label& label) const {
+  // Forward to the non-const version.
+  return const_cast<Builder*>(this)->GetRecord(label);
+}
+
+BuilderRecord* Builder::GetRecord(const Label& label) {
+  auto found = records_.find(label);
+  return (found != records_.end()) ? found->second.get() : nullptr;
+}
+
+bool Builder::CheckForBadItems(Err* err) const {
+  // Look for errors where we find a defined node with an item that refers to
+  // an undefined one with no item. There may be other nodes in turn depending
+  // on our defined one, but listing those isn't helpful: we want to find the
+  // broken link.
+  //
+  // This finds normal "missing dependency" errors but does not find circular
+  // dependencies because in this case all items in the cycle will be GENERATED
+  // but none will be resolved. If this happens, we'll check explicitly for
+  // that below.
+  std::vector<const BuilderRecord*> bad_records;
+  std::string depstring;
+  for (const auto& record_pair : records_) {
+    const BuilderRecord* src = record_pair.second.get();
+    if (!src->should_generate())
+      continue;  // Skip ungenerated nodes.
+
+    if (!src->resolved()) {
+      bad_records.push_back(src);
+
+      // Check dependencies.
+      for (auto* dest : src->unresolved_deps()) {
+        if (!dest->item()) {
+          depstring += src->label().GetUserVisibleName(true) + "\n  needs " +
+                       dest->label().GetUserVisibleName(true) + "\n";
+        }
+      }
+    }
+  }
+
+  if (!depstring.empty()) {
+    *err = Err(Location(), "Unresolved dependencies.", depstring);
+    return false;
+  }
+
+  if (!bad_records.empty()) {
+    // Our logic above found a bad node but didn't identify the problem. This
+    // normally means a circular dependency.
+    depstring = CheckForCircularDependencies(bad_records);
+    if (depstring.empty()) {
+      // Something's very wrong, just dump out the bad nodes.
+      depstring =
+          "I have no idea what went wrong, but these are unresolved, "
+          "possibly due to an\ninternal error:";
+      for (auto* bad_record : bad_records) {
+        depstring +=
+            "\n\"" + bad_record->label().GetUserVisibleName(false) + "\"";
+      }
+      *err = Err(Location(), "", depstring);
+    } else {
+      *err = Err(Location(), "Dependency cycle:", depstring);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool Builder::TargetDefined(BuilderRecord* record, Err* err) {
+  Target* target = record->item()->AsTarget();
+
+  if (!AddDeps(record, target->public_deps(), err) ||
+      !AddDeps(record, target->private_deps(), err) ||
+      !AddDeps(record, target->data_deps(), err) ||
+      !AddDeps(record, target->configs().vector(), err) ||
+      !AddDeps(record, target->all_dependent_configs(), err) ||
+      !AddDeps(record, target->public_configs(), err) ||
+      !AddActionValuesDep(record, target->action_values(), err) ||
+      !AddToolchainDep(record, target, err))
+    return false;
+
+  // All targets in the default toolchain get generated by default. We also
+  // check if this target was previously marked as "required" and force setting
+  // the bit again so the target's dependencies (which we now know) get the
+  // required bit pushed to them.
+  if (record->should_generate() || target->settings()->is_default())
+    RecursiveSetShouldGenerate(record, true);
+
+  return true;
+}
+
+bool Builder::ConfigDefined(BuilderRecord* record, Err* err) {
+  Config* config = record->item()->AsConfig();
+  if (!AddDeps(record, config->configs(), err))
+    return false;
+
+  // Make sure all deps of this config are scheduled to be loaded. For other
+  // item types like targets, the "should generate" flag is propagated around
+  // to mark whether this should happen. We could call
+  // RecursiveSetShouldGenerate to do this step here, but since configs nor
+  // anything they depend on is actually written, the "generate" flag isn't
+  // relevant and means extra book keeping. Just force load any deps of this
+  // config.
+  for (auto* cur : record->all_deps())
+    ScheduleItemLoadIfNecessary(cur);
+
+  return true;
+}
+
+bool Builder::ToolchainDefined(BuilderRecord* record, Err* err) {
+  Toolchain* toolchain = record->item()->AsToolchain();
+
+  if (!AddDeps(record, toolchain->deps(), err))
+    return false;
+
+  for (const auto& tool : toolchain->tools()) {
+    if (tool.second->pool().label.is_null())
+      continue;
+
+    BuilderRecord* dep_record = GetOrCreateRecordOfType(
+        tool.second->pool().label, tool.second->pool().origin,
+        BuilderRecord::ITEM_POOL, err);
+    if (!dep_record)
+      return false;
+    record->AddDep(dep_record);
+  }
+
+  // The default toolchain gets generated by default. Also propagate the
+  // generate flag if it depends on items in a non-default toolchain.
+  if (record->should_generate() ||
+      toolchain->settings()->default_toolchain_label() == toolchain->label())
+    RecursiveSetShouldGenerate(record, true);
+
+  loader_->ToolchainLoaded(toolchain);
+  return true;
+}
+
+BuilderRecord* Builder::GetOrCreateRecordOfType(const Label& label,
+                                                const ParseNode* request_from,
+                                                BuilderRecord::ItemType type,
+                                                Err* err) {
+  BuilderRecord* record = GetRecord(label);
+  if (!record) {
+    // Not seen this record yet, create a new one.
+    auto new_record = std::make_unique<BuilderRecord>(type, label);
+    new_record->set_originally_referenced_from(request_from);
+    record = new_record.get();
+    records_[label] = std::move(new_record);
+    return record;
+  }
+
+  // Check types.
+  if (record->type() != type) {
+    std::string msg =
+        "The type of " + label.GetUserVisibleName(false) + "\nhere is a " +
+        BuilderRecord::GetNameForType(type) + " but was previously seen as a " +
+        BuilderRecord::GetNameForType(record->type()) +
+        ".\n\n"
+        "The most common cause is that the label of a config was put in the\n"
+        "in the deps section of a target (or vice-versa).";
+    *err = Err(request_from, "Item type does not match.", msg);
+    if (record->originally_referenced_from()) {
+      err->AppendSubErr(
+          Err(record->originally_referenced_from(), std::string()));
+    }
+    return nullptr;
+  }
+
+  return record;
+}
+
+BuilderRecord* Builder::GetResolvedRecordOfType(const Label& label,
+                                                const ParseNode* origin,
+                                                BuilderRecord::ItemType type,
+                                                Err* err) {
+  BuilderRecord* record = GetRecord(label);
+  if (!record) {
+    *err = Err(origin, "Item not found",
+               "\"" + label.GetUserVisibleName(false) +
+                   "\" doesn't\n"
+                   "refer to an existent thing.");
+    return nullptr;
+  }
+
+  const Item* item = record->item();
+  if (!item) {
+    *err = Err(
+        origin, "Item not resolved.",
+        "\"" + label.GetUserVisibleName(false) + "\" hasn't been resolved.\n");
+    return nullptr;
+  }
+
+  if (!BuilderRecord::IsItemOfType(item, type)) {
+    *err =
+        Err(origin,
+            std::string("This is not a ") + BuilderRecord::GetNameForType(type),
+            "\"" + label.GetUserVisibleName(false) + "\" refers to a " +
+                item->GetItemTypeName() + " instead of a " +
+                BuilderRecord::GetNameForType(type) + ".");
+    return nullptr;
+  }
+  return record;
+}
+
+bool Builder::AddDeps(BuilderRecord* record,
+                      const LabelConfigVector& configs,
+                      Err* err) {
+  for (const auto& config : configs) {
+    BuilderRecord* dep_record = GetOrCreateRecordOfType(
+        config.label, config.origin, BuilderRecord::ITEM_CONFIG, err);
+    if (!dep_record)
+      return false;
+    record->AddDep(dep_record);
+  }
+  return true;
+}
+
+bool Builder::AddDeps(BuilderRecord* record,
+                      const UniqueVector<LabelConfigPair>& configs,
+                      Err* err) {
+  for (const auto& config : configs) {
+    BuilderRecord* dep_record = GetOrCreateRecordOfType(
+        config.label, config.origin, BuilderRecord::ITEM_CONFIG, err);
+    if (!dep_record)
+      return false;
+    record->AddDep(dep_record);
+  }
+  return true;
+}
+
+bool Builder::AddDeps(BuilderRecord* record,
+                      const LabelTargetVector& targets,
+                      Err* err) {
+  for (const auto& target : targets) {
+    BuilderRecord* dep_record = GetOrCreateRecordOfType(
+        target.label, target.origin, BuilderRecord::ITEM_TARGET, err);
+    if (!dep_record)
+      return false;
+    record->AddDep(dep_record);
+  }
+  return true;
+}
+
+bool Builder::AddActionValuesDep(BuilderRecord* record,
+                                 const ActionValues& action_values,
+                                 Err* err) {
+  if (action_values.pool().label.is_null())
+    return true;
+
+  BuilderRecord* pool_record = GetOrCreateRecordOfType(
+      action_values.pool().label, action_values.pool().origin,
+      BuilderRecord::ITEM_POOL, err);
+  if (!pool_record)
+    return false;
+  record->AddDep(pool_record);
+
+  return true;
+}
+
+bool Builder::AddToolchainDep(BuilderRecord* record,
+                              const Target* target,
+                              Err* err) {
+  BuilderRecord* toolchain_record = GetOrCreateRecordOfType(
+      target->settings()->toolchain_label(), target->defined_from(),
+      BuilderRecord::ITEM_TOOLCHAIN, err);
+  if (!toolchain_record)
+    return false;
+  record->AddDep(toolchain_record);
+
+  return true;
+}
+
+void Builder::RecursiveSetShouldGenerate(BuilderRecord* record, bool force) {
+  if (!record->should_generate()) {
+    record->set_should_generate(true);
+
+    // This may have caused the item to go into "resolved and generated" state.
+    if (record->resolved() && resolved_and_generated_callback_)
+      resolved_and_generated_callback_(record);
+  } else if (!force) {
+    return;  // Already set and we're not required to iterate dependencies.
+  }
+
+  for (auto* cur : record->all_deps()) {
+    if (!cur->should_generate()) {
+      ScheduleItemLoadIfNecessary(cur);
+      RecursiveSetShouldGenerate(cur, false);
+    }
+  }
+}
+
+void Builder::ScheduleItemLoadIfNecessary(BuilderRecord* record) {
+  const ParseNode* origin = record->originally_referenced_from();
+  loader_->Load(record->label(), origin ? origin->GetRange() : LocationRange());
+}
+
+bool Builder::ResolveItem(BuilderRecord* record, Err* err) {
+  DCHECK(record->can_resolve() && !record->resolved());
+
+  if (record->type() == BuilderRecord::ITEM_TARGET) {
+    Target* target = record->item()->AsTarget();
+    if (!ResolveDeps(&target->public_deps(), err) ||
+        !ResolveDeps(&target->private_deps(), err) ||
+        !ResolveDeps(&target->data_deps(), err) ||
+        !ResolveConfigs(&target->configs(), err) ||
+        !ResolveConfigs(&target->all_dependent_configs(), err) ||
+        !ResolveConfigs(&target->public_configs(), err) ||
+        !ResolveActionValues(&target->action_values(), err) ||
+        !ResolveToolchain(target, err))
+      return false;
+  } else if (record->type() == BuilderRecord::ITEM_CONFIG) {
+    Config* config = record->item()->AsConfig();
+    if (!ResolveConfigs(&config->configs(), err))
+      return false;
+  } else if (record->type() == BuilderRecord::ITEM_TOOLCHAIN) {
+    Toolchain* toolchain = record->item()->AsToolchain();
+    if (!ResolveDeps(&toolchain->deps(), err))
+      return false;
+    if (!ResolvePools(toolchain, err))
+      return false;
+  }
+
+  record->set_resolved(true);
+
+  if (!record->item()->OnResolved(err))
+    return false;
+  if (record->should_generate() && resolved_and_generated_callback_)
+    resolved_and_generated_callback_(record);
+
+  // Recursively update everybody waiting on this item to be resolved.
+  for (BuilderRecord* waiting : record->waiting_on_resolution()) {
+    DCHECK(waiting->unresolved_deps().find(record) !=
+           waiting->unresolved_deps().end());
+    waiting->unresolved_deps().erase(record);
+
+    if (waiting->can_resolve()) {
+      if (!ResolveItem(waiting, err))
+        return false;
+    }
+  }
+  record->waiting_on_resolution().clear();
+  return true;
+}
+
+bool Builder::ResolveDeps(LabelTargetVector* deps, Err* err) {
+  for (LabelTargetPair& cur : *deps) {
+    DCHECK(!cur.ptr);
+
+    BuilderRecord* record = GetResolvedRecordOfType(
+        cur.label, cur.origin, BuilderRecord::ITEM_TARGET, err);
+    if (!record)
+      return false;
+    cur.ptr = record->item()->AsTarget();
+  }
+  return true;
+}
+
+bool Builder::ResolveConfigs(UniqueVector<LabelConfigPair>* configs, Err* err) {
+  for (const auto& cur : *configs) {
+    DCHECK(!cur.ptr);
+
+    BuilderRecord* record = GetResolvedRecordOfType(
+        cur.label, cur.origin, BuilderRecord::ITEM_CONFIG, err);
+    if (!record)
+      return false;
+    const_cast<LabelConfigPair&>(cur).ptr = record->item()->AsConfig();
+  }
+  return true;
+}
+
+bool Builder::ResolveToolchain(Target* target, Err* err) {
+  BuilderRecord* record = GetResolvedRecordOfType(
+      target->settings()->toolchain_label(), target->defined_from(),
+      BuilderRecord::ITEM_TOOLCHAIN, err);
+  if (!record) {
+    *err = Err(
+        target->defined_from(), "Toolchain for target not defined.",
+        "I was hoping to find a toolchain " +
+            target->settings()->toolchain_label().GetUserVisibleName(false));
+    return false;
+  }
+
+  if (!target->SetToolchain(record->item()->AsToolchain(), err))
+    return false;
+
+  return true;
+}
+
+bool Builder::ResolveActionValues(ActionValues* action_values, Err* err) {
+  if (action_values->pool().label.is_null())
+    return true;
+
+  BuilderRecord* record = GetResolvedRecordOfType(
+      action_values->pool().label, action_values->pool().origin,
+      BuilderRecord::ITEM_POOL, err);
+  if (!record)
+    return false;
+  action_values->set_pool(LabelPtrPair<Pool>(record->item()->AsPool()));
+
+  return true;
+}
+
+bool Builder::ResolvePools(Toolchain* toolchain, Err* err) {
+  for (const auto& tool : toolchain->tools()) {
+    if (tool.second->pool().label.is_null())
+      continue;
+
+    BuilderRecord* record = GetResolvedRecordOfType(
+        tool.second->pool().label, toolchain->defined_from(),
+        BuilderRecord::ITEM_POOL, err);
+    if (!record) {
+      *err = Err(tool.second->pool().origin, "Pool for tool not defined.",
+                 "I was hoping to find a pool " +
+                     tool.second->pool().label.GetUserVisibleName(false));
+      return false;
+    }
+
+    tool.second->set_pool(LabelPtrPair<Pool>(record->item()->AsPool()));
+  }
+
+  return true;
+}
+
+std::string Builder::CheckForCircularDependencies(
+    const std::vector<const BuilderRecord*>& bad_records) const {
+  std::vector<const BuilderRecord*> cycle;
+  if (!RecursiveFindCycle(bad_records[0], &cycle))
+    return std::string();  // Didn't find a cycle, something else is wrong.
+
+  std::string ret;
+  for (size_t i = 0; i < cycle.size(); i++) {
+    ret += "  " + cycle[i]->label().GetUserVisibleName(loader_->GetDefaultToolchain());
+    if (i != cycle.size() - 1)
+      ret += " ->";
+    ret += "\n";
+  }
+
+  return ret;
+}
diff --git a/src/gn/builder.h b/src/gn/builder.h
new file mode 100644 (file)
index 0000000..9f86826
--- /dev/null
@@ -0,0 +1,146 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUILDER_H_
+#define TOOLS_GN_BUILDER_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "gn/builder_record.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/unique_vector.h"
+
+class ActionValues;
+class Err;
+class Loader;
+class ParseNode;
+
+// The builder assembles the dependency tree. It is not threadsafe and runs on
+// the main thread only. See also BuilderRecord.
+class Builder {
+ public:
+  using ResolvedGeneratedCallback = std::function<void(const BuilderRecord*)>;
+
+  explicit Builder(Loader* loader);
+  ~Builder();
+
+  // The resolved callback is called when a target has been both resolved and
+  // marked generated. This will be executed only on the main thread.
+  void set_resolved_and_generated_callback(
+      const ResolvedGeneratedCallback& cb) {
+    resolved_and_generated_callback_ = cb;
+  }
+
+  Loader* loader() const { return loader_; }
+
+  void ItemDefined(std::unique_ptr<Item> item);
+
+  // Returns NULL if there is not a thing with the corresponding label.
+  const Item* GetItem(const Label& label) const;
+  const Toolchain* GetToolchain(const Label& label) const;
+
+  std::vector<const BuilderRecord*> GetAllRecords() const;
+
+  // Returns items which should be generated and which are defined.
+  std::vector<const Item*> GetAllResolvedItems() const;
+
+  // Returns targets which should be generated and which are defined.
+  std::vector<const Target*> GetAllResolvedTargets() const;
+
+  // Returns the record for the given label, or NULL if it doesn't exist.
+  // Mostly used for unit tests.
+  const BuilderRecord* GetRecord(const Label& label) const;
+  BuilderRecord* GetRecord(const Label& label);
+
+  // If there are any undefined references, returns false and sets the error.
+  bool CheckForBadItems(Err* err) const;
+
+ private:
+  bool TargetDefined(BuilderRecord* record, Err* err);
+  bool ConfigDefined(BuilderRecord* record, Err* err);
+  bool ToolchainDefined(BuilderRecord* record, Err* err);
+
+  // Returns the record associated with the given label. This function checks
+  // that if we already have references for it, the type matches. If no record
+  // exists yet, a new one will be created.
+  //
+  // If any of the conditions fail, the return value will be null and the error
+  // will be set. request_from is used as the source of the error.
+  BuilderRecord* GetOrCreateRecordOfType(const Label& label,
+                                         const ParseNode* request_from,
+                                         BuilderRecord::ItemType type,
+                                         Err* err);
+
+  // Returns the record associated with the given label. This function checks
+  // that it's already been resolved to the correct type.
+  //
+  // If any of the conditions fail, the return value will be null and the error
+  // will be set. request_from is used as the source of the error.
+  BuilderRecord* GetResolvedRecordOfType(const Label& label,
+                                         const ParseNode* request_from,
+                                         BuilderRecord::ItemType type,
+                                         Err* err);
+
+  bool AddDeps(BuilderRecord* record,
+               const LabelConfigVector& configs,
+               Err* err);
+  bool AddDeps(BuilderRecord* record,
+               const UniqueVector<LabelConfigPair>& configs,
+               Err* err);
+  bool AddDeps(BuilderRecord* record,
+               const LabelTargetVector& targets,
+               Err* err);
+  bool AddActionValuesDep(BuilderRecord* record,
+                          const ActionValues& action_values,
+                          Err* err);
+  bool AddToolchainDep(BuilderRecord* record, const Target* target, Err* err);
+
+  // Given a target, sets the "should generate" bit and pushes it through the
+  // dependency tree. Any time the bit it set, we ensure that the given item is
+  // scheduled to be loaded.
+  //
+  // If the force flag is set, we'll ignore the current state of the record's
+  // should_generate flag, and set it on the dependents every time. This is
+  // used when defining a target: the "should generate" may have been set
+  // before the item was defined (if it is required by something that is
+  // required). In this case, we need to re-push the "should generate" flag
+  // to the item's dependencies.
+  void RecursiveSetShouldGenerate(BuilderRecord* record, bool force);
+
+  void ScheduleItemLoadIfNecessary(BuilderRecord* record);
+
+  // This takes a BuilderRecord with resolved dependencies, and fills in the
+  // target's Label*Vectors with the resolved pointers.
+  bool ResolveItem(BuilderRecord* record, Err* err);
+
+  // Fills in the pointers in the given vector based on the labels. We assume
+  // that everything should be resolved by this point, so will return an error
+  // if anything isn't found or if the type doesn't match.
+  bool ResolveDeps(LabelTargetVector* deps, Err* err);
+  bool ResolveConfigs(UniqueVector<LabelConfigPair>* configs, Err* err);
+  bool ResolveActionValues(ActionValues* action_values, Err* err);
+  bool ResolveToolchain(Target* target, Err* err);
+  bool ResolvePools(Toolchain* toolchain, Err* err);
+
+  // Given a list of unresolved records, tries to find any circular
+  // dependencies and returns the string describing the problem. If no circular
+  // deps were found, returns the empty string.
+  std::string CheckForCircularDependencies(
+      const std::vector<const BuilderRecord*>& bad_records) const;
+
+  // Non owning pointer.
+  Loader* loader_;
+
+  std::map<Label, std::unique_ptr<BuilderRecord>> records_;
+
+  ResolvedGeneratedCallback resolved_and_generated_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(Builder);
+};
+
+#endif  // TOOLS_GN_BUILDER_H_
diff --git a/src/gn/builder_record.cc b/src/gn/builder_record.cc
new file mode 100644 (file)
index 0000000..cbe8dae
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/builder_record.h"
+
+#include "gn/item.h"
+
+BuilderRecord::BuilderRecord(ItemType type, const Label& label)
+    : type_(type), label_(label) {}
+
+// static
+const char* BuilderRecord::GetNameForType(ItemType type) {
+  switch (type) {
+    case ITEM_TARGET:
+      return "target";
+    case ITEM_CONFIG:
+      return "config";
+    case ITEM_TOOLCHAIN:
+      return "toolchain";
+    case ITEM_POOL:
+      return "pool";
+    case ITEM_UNKNOWN:
+    default:
+      return "unknown";
+  }
+}
+
+// static
+bool BuilderRecord::IsItemOfType(const Item* item, ItemType type) {
+  switch (type) {
+    case ITEM_TARGET:
+      return !!item->AsTarget();
+    case ITEM_CONFIG:
+      return !!item->AsConfig();
+    case ITEM_TOOLCHAIN:
+      return !!item->AsToolchain();
+    case ITEM_POOL:
+      return !!item->AsPool();
+    case ITEM_UNKNOWN:
+    default:
+      return false;
+  }
+}
+
+// static
+BuilderRecord::ItemType BuilderRecord::TypeOfItem(const Item* item) {
+  if (item->AsTarget())
+    return ITEM_TARGET;
+  if (item->AsConfig())
+    return ITEM_CONFIG;
+  if (item->AsToolchain())
+    return ITEM_TOOLCHAIN;
+  if (item->AsPool())
+    return ITEM_POOL;
+
+  NOTREACHED();
+  return ITEM_UNKNOWN;
+}
+
+void BuilderRecord::AddDep(BuilderRecord* record) {
+  all_deps_.insert(record);
+  if (!record->resolved()) {
+    unresolved_deps_.insert(record);
+    record->waiting_on_resolution_.insert(this);
+  }
+}
diff --git a/src/gn/builder_record.h b/src/gn/builder_record.h
new file mode 100644 (file)
index 0000000..1ccb19a
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUILDER_RECORD_H_
+#define TOOLS_GN_BUILDER_RECORD_H_
+
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/macros.h"
+#include "gn/item.h"
+#include "gn/location.h"
+
+class ParseNode;
+
+// This class is used by the builder to manage the loading of the dependency
+// tree. It holds a reference to an item and links to other records that the
+// item depends on, both resolved ones, and unresolved ones.
+//
+// If a target depends on another one that hasn't been defined yet, we'll make
+// a placeholder BuilderRecord with no item, and try to load the buildfile
+// associated with the new item. The item will get filled in when we encounter
+// the declaration for the item (or when we're done and realize there are
+// undefined items).
+//
+// You can also have null item pointers when the target is not required for
+// the current build (should_generate is false).
+class BuilderRecord {
+ public:
+  using BuilderRecordSet = std::set<BuilderRecord*>;
+
+  enum ItemType {
+    ITEM_UNKNOWN,
+    ITEM_TARGET,
+    ITEM_CONFIG,
+    ITEM_TOOLCHAIN,
+    ITEM_POOL
+  };
+
+  BuilderRecord(ItemType type, const Label& label);
+
+  ItemType type() const { return type_; }
+  const Label& label() const { return label_; }
+
+  // Returns a user-ready name for the given type. e.g. "target".
+  static const char* GetNameForType(ItemType type);
+
+  // Returns true if the given item is of the given type.
+  static bool IsItemOfType(const Item* item, ItemType type);
+
+  // Returns the type enum for the given item.
+  static ItemType TypeOfItem(const Item* item);
+
+  Item* item() { return item_.get(); }
+  const Item* item() const { return item_.get(); }
+  void set_item(std::unique_ptr<Item> item) { item_ = std::move(item); }
+
+  // Indicates from where this item was originally referenced from that caused
+  // it to be loaded. For targets for which we encountered the declaration
+  // before a reference, this will be the empty range.
+  const ParseNode* originally_referenced_from() const {
+    return originally_referenced_from_;
+  }
+  void set_originally_referenced_from(const ParseNode* pn) {
+    originally_referenced_from_ = pn;
+  }
+
+  bool should_generate() const { return should_generate_; }
+  void set_should_generate(bool sg) { should_generate_ = sg; }
+
+  bool resolved() const { return resolved_; }
+  void set_resolved(bool r) { resolved_ = r; }
+
+  bool can_resolve() const { return item_ && unresolved_deps_.empty(); }
+
+  // All records this one is depending on.
+  BuilderRecordSet& all_deps() { return all_deps_; }
+  const BuilderRecordSet& all_deps() const { return all_deps_; }
+
+  // Unresolved records this one is depending on. A subset of all... above.
+  BuilderRecordSet& unresolved_deps() { return unresolved_deps_; }
+  const BuilderRecordSet& unresolved_deps() const { return unresolved_deps_; }
+
+  // Records that are waiting on this one to be resolved. This is the other
+  // end of the "unresolved deps" arrow.
+  BuilderRecordSet& waiting_on_resolution() { return waiting_on_resolution_; }
+  const BuilderRecordSet& waiting_on_resolution() const {
+    return waiting_on_resolution_;
+  }
+
+  void AddDep(BuilderRecord* record);
+
+ private:
+  ItemType type_;
+  Label label_;
+  std::unique_ptr<Item> item_;
+  const ParseNode* originally_referenced_from_ = nullptr;
+  bool should_generate_ = false;
+  bool resolved_ = false;
+
+  BuilderRecordSet all_deps_;
+  BuilderRecordSet unresolved_deps_;
+  BuilderRecordSet waiting_on_resolution_;
+
+  DISALLOW_COPY_AND_ASSIGN(BuilderRecord);
+};
+
+#endif  // TOOLS_GN_BUILDER_RECORD_H_
diff --git a/src/gn/builder_unittest.cc b/src/gn/builder_unittest.cc
new file mode 100644 (file)
index 0000000..b0f48f0
--- /dev/null
@@ -0,0 +1,247 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/builder.h"
+#include "gn/config.h"
+#include "gn/loader.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "gn/toolchain.h"
+#include "util/test/test.h"
+
+namespace gn_builder_unittest {
+
+class MockLoader : public Loader {
+ public:
+  MockLoader() = default;
+
+  // Loader implementation:
+  void Load(const SourceFile& file,
+            const LocationRange& origin,
+            const Label& toolchain_name) override {
+    files_.push_back(file);
+  }
+  void ToolchainLoaded(const Toolchain* toolchain) override {}
+  Label GetDefaultToolchain() const override { return Label(); }
+  const Settings* GetToolchainSettings(const Label& label) const override {
+    return nullptr;
+  }
+  SourceFile BuildFileForLabel(const Label& label) const override {
+    return SourceFile(label.dir().value() + "BUILD.gn");
+  }
+
+  bool HasLoadedNone() const { return files_.empty(); }
+
+  // Returns true if one/two loads have been requested and they match the given
+  // file(s). This will clear the records so it will be empty for the next call.
+  bool HasLoadedOne(const SourceFile& file) {
+    if (files_.size() != 1u) {
+      files_.clear();
+      return false;
+    }
+    bool match = (files_[0] == file);
+    files_.clear();
+    return match;
+  }
+  bool HasLoadedTwo(const SourceFile& a, const SourceFile& b) {
+    if (files_.size() != 2u) {
+      files_.clear();
+      return false;
+    }
+
+    bool match = ((files_[0] == a && files_[1] == b) ||
+                  (files_[0] == b && files_[1] == a));
+    files_.clear();
+    return match;
+  }
+
+ private:
+  ~MockLoader() override = default;
+
+  std::vector<SourceFile> files_;
+};
+
+class BuilderTest : public testing::Test {
+ public:
+  BuilderTest()
+      : loader_(new MockLoader),
+        builder_(loader_.get()),
+        settings_(&build_settings_, std::string()),
+        scope_(&settings_) {
+    build_settings_.SetBuildDir(SourceDir("//out/"));
+    settings_.set_toolchain_label(Label(SourceDir("//tc/"), "default"));
+    settings_.set_default_toolchain_label(settings_.toolchain_label());
+  }
+
+  Toolchain* DefineToolchain() {
+    Toolchain* tc = new Toolchain(&settings_, settings_.toolchain_label());
+    TestWithScope::SetupToolchain(tc);
+    builder_.ItemDefined(std::unique_ptr<Item>(tc));
+    return tc;
+  }
+
+ protected:
+  scoped_refptr<MockLoader> loader_;
+  Builder builder_;
+  BuildSettings build_settings_;
+  Settings settings_;
+  Scope scope_;
+};
+
+TEST_F(BuilderTest, BasicDeps) {
+  SourceDir toolchain_dir = settings_.toolchain_label().dir();
+  std::string toolchain_name = settings_.toolchain_label().name();
+
+  // Construct a dependency chain: A -> B -> C. Define A first with a
+  // forward-reference to B, then C, then B to test the different orders that
+  // the dependencies are hooked up.
+  Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
+  Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
+  Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
+
+  // The builder will take ownership of the pointers.
+  Target* a = new Target(&settings_, a_label);
+  a->public_deps().push_back(LabelTargetPair(b_label));
+  a->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(a));
+
+  // Should have requested that B and the toolchain is loaded.
+  EXPECT_TRUE(loader_->HasLoadedTwo(SourceFile("//tc/BUILD.gn"),
+                                    SourceFile("//b/BUILD.gn")));
+
+  // Define the toolchain.
+  DefineToolchain();
+  BuilderRecord* toolchain_record =
+      builder_.GetRecord(settings_.toolchain_label());
+  ASSERT_TRUE(toolchain_record);
+  EXPECT_EQ(BuilderRecord::ITEM_TOOLCHAIN, toolchain_record->type());
+
+  // A should be unresolved with an item
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  EXPECT_TRUE(a_record->item());
+  EXPECT_FALSE(a_record->resolved());
+  EXPECT_FALSE(a_record->can_resolve());
+
+  // B should be unresolved, have no item, and no deps.
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+  EXPECT_FALSE(b_record->item());
+  EXPECT_FALSE(b_record->resolved());
+  EXPECT_FALSE(b_record->can_resolve());
+  EXPECT_TRUE(b_record->all_deps().empty());
+
+  // A should have two deps: B and the toolchain. Only B should be unresolved.
+  EXPECT_EQ(2u, a_record->all_deps().size());
+  EXPECT_EQ(1u, a_record->unresolved_deps().size());
+  EXPECT_NE(a_record->all_deps().end(),
+            a_record->all_deps().find(toolchain_record));
+  EXPECT_NE(a_record->all_deps().end(), a_record->all_deps().find(b_record));
+  EXPECT_NE(a_record->unresolved_deps().end(),
+            a_record->unresolved_deps().find(b_record));
+
+  // B should be marked as having A waiting on it.
+  EXPECT_EQ(1u, b_record->waiting_on_resolution().size());
+  EXPECT_NE(b_record->waiting_on_resolution().end(),
+            b_record->waiting_on_resolution().find(a_record));
+
+  // Add the C target.
+  Target* c = new Target(&settings_, c_label);
+  c->set_output_type(Target::STATIC_LIBRARY);
+  c->visibility().SetPublic();
+  builder_.ItemDefined(std::unique_ptr<Item>(c));
+
+  // C only depends on the already-loaded toolchain so we shouldn't have
+  // requested anything else.
+  EXPECT_TRUE(loader_->HasLoadedNone());
+
+  // Add the B target.
+  Target* b = new Target(&settings_, b_label);
+  a->public_deps().push_back(LabelTargetPair(c_label));
+  b->set_output_type(Target::SHARED_LIBRARY);
+  b->visibility().SetPublic();
+  builder_.ItemDefined(std::unique_ptr<Item>(b));
+
+  // B depends only on the already-loaded C and toolchain so we shouldn't have
+  // requested anything else.
+  EXPECT_TRUE(loader_->HasLoadedNone());
+
+  // All targets should now be resolved.
+  BuilderRecord* c_record = builder_.GetRecord(c_label);
+  EXPECT_TRUE(a_record->resolved());
+  EXPECT_TRUE(b_record->resolved());
+  EXPECT_TRUE(c_record->resolved());
+
+  EXPECT_TRUE(a_record->unresolved_deps().empty());
+  EXPECT_TRUE(b_record->unresolved_deps().empty());
+  EXPECT_TRUE(c_record->unresolved_deps().empty());
+
+  EXPECT_TRUE(a_record->waiting_on_resolution().empty());
+  EXPECT_TRUE(b_record->waiting_on_resolution().empty());
+  EXPECT_TRUE(c_record->waiting_on_resolution().empty());
+}
+
+// Tests that the "should generate" flag is set and propagated properly.
+TEST_F(BuilderTest, ShouldGenerate) {
+  DefineToolchain();
+
+  // Define a secondary toolchain.
+  Settings settings2(&build_settings_, "secondary/");
+  Label toolchain_label2(SourceDir("//tc/"), "secondary");
+  settings2.set_toolchain_label(toolchain_label2);
+  Toolchain* tc2 = new Toolchain(&settings2, toolchain_label2);
+  TestWithScope::SetupToolchain(tc2);
+  builder_.ItemDefined(std::unique_ptr<Item>(tc2));
+
+  // Construct a dependency chain: A -> B. A is in the default toolchain, B
+  // is not.
+  Label a_label(SourceDir("//foo/"), "a", settings_.toolchain_label().dir(),
+                "a");
+  Label b_label(SourceDir("//foo/"), "b", toolchain_label2.dir(),
+                toolchain_label2.name());
+
+  // First define B.
+  Target* b = new Target(&settings2, b_label);
+  b->visibility().SetPublic();
+  b->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(b));
+
+  // B should not be marked generated by default.
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+  EXPECT_FALSE(b_record->should_generate());
+
+  // Define A with a dependency on B.
+  Target* a = new Target(&settings_, a_label);
+  a->public_deps().push_back(LabelTargetPair(b_label));
+  a->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(a));
+
+  // A should have the generate bit set since it's in the default toolchain.
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  EXPECT_TRUE(a_record->should_generate());
+
+  // It should have gotten pushed to B.
+  EXPECT_TRUE(b_record->should_generate());
+}
+
+// Tests that configs applied to a config get loaded (bug 536844).
+TEST_F(BuilderTest, ConfigLoad) {
+  SourceDir toolchain_dir = settings_.toolchain_label().dir();
+  std::string toolchain_name = settings_.toolchain_label().name();
+
+  // Construct a dependency chain: A -> B -> C. Define A first with a
+  // forward-reference to B, then C, then B to test the different orders that
+  // the dependencies are hooked up.
+  Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
+  Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
+  Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
+
+  // The builder will take ownership of the pointers.
+  Config* a = new Config(&settings_, a_label);
+  a->configs().push_back(LabelConfigPair(b_label));
+  builder_.ItemDefined(std::unique_ptr<Item>(a));
+
+  // Should have requested that B is loaded.
+  EXPECT_TRUE(loader_->HasLoadedOne(SourceFile("//b/BUILD.gn")));
+}
+
+}  // namespace gn_builder_unittest
diff --git a/src/gn/bundle_data.cc b/src/gn/bundle_data.cc
new file mode 100644 (file)
index 0000000..6b7fe77
--- /dev/null
@@ -0,0 +1,195 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/bundle_data.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/label_pattern.h"
+#include "gn/output_file.h"
+#include "gn/settings.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+namespace {
+
+// Return directory of |path| without the trailing directory separator.
+std::string_view FindDirNoTrailingSeparator(std::string_view path) {
+  std::string_view::size_type pos = path.find_last_of("/\\");
+  if (pos == std::string_view::npos)
+    return std::string_view();
+  return std::string_view(path.data(), pos);
+}
+
+bool IsSourceFileFromAssetsCatalog(std::string_view source,
+                                   SourceFile* asset_catalog) {
+  // Check whether |source| matches one of the following pattern:
+  //    .*\.xcassets/Contents.json
+  //    .*\.xcassets/[^/]*\.appiconset/[^/]*
+  //    .*\.xcassets/[^/]*\.colorset/[^/]*
+  //    .*\.xcassets/[^/]*\.dataset/[^/]*
+  //    .*\.xcassets/[^/]*\.imageset/[^/]*
+  //    .*\.xcassets/[^/]*\.launchimage/[^/]*
+  //    .*\.xcassets/[^/]*\.symbolset/[^/]*
+  bool is_file_from_asset_catalog = false;
+  std::string_view dir = FindDirNoTrailingSeparator(source);
+  if (base::EndsWith(source, "/Contents.json", base::CompareCase::SENSITIVE) &&
+      base::EndsWith(dir, ".xcassets", base::CompareCase::SENSITIVE)) {
+    is_file_from_asset_catalog = true;
+  } else if (base::EndsWith(dir, ".appiconset", base::CompareCase::SENSITIVE) ||
+             base::EndsWith(dir, ".colorset", base::CompareCase::SENSITIVE) ||
+             base::EndsWith(dir, ".dataset", base::CompareCase::SENSITIVE) ||
+             base::EndsWith(dir, ".imageset", base::CompareCase::SENSITIVE) ||
+             base::EndsWith(dir, ".launchimage",
+                            base::CompareCase::SENSITIVE) ||
+             base::EndsWith(dir, ".symbolset", base::CompareCase::SENSITIVE)) {
+    dir = FindDirNoTrailingSeparator(dir);
+    is_file_from_asset_catalog =
+        base::EndsWith(dir, ".xcassets", base::CompareCase::SENSITIVE);
+  }
+  if (is_file_from_asset_catalog && asset_catalog) {
+    std::string asset_catalog_path(dir);
+    *asset_catalog = SourceFile(std::move(asset_catalog_path));
+  }
+  return is_file_from_asset_catalog;
+}
+
+}  // namespace
+
+BundleData::BundleData() = default;
+
+BundleData::~BundleData() = default;
+
+void BundleData::AddBundleData(const Target* target) {
+  DCHECK_EQ(target->output_type(), Target::BUNDLE_DATA);
+  for (const auto& pattern : bundle_deps_filter_) {
+    if (pattern.Matches(target->label()))
+      return;
+  }
+  bundle_deps_.push_back(target);
+}
+
+void BundleData::OnTargetResolved(Target* owning_target) {
+  // Only initialize file_rules_ and assets_catalog_sources for "create_bundle"
+  // target (properties are only used by those targets).
+  if (owning_target->output_type() != Target::CREATE_BUNDLE)
+    return;
+
+  UniqueVector<const Target*> assets_catalog_deps;
+  UniqueVector<SourceFile> assets_catalog_sources;
+
+  for (const Target* target : bundle_deps_) {
+    SourceFiles file_rule_sources;
+    for (const SourceFile& source_file : target->sources()) {
+      SourceFile assets_catalog;
+      if (IsSourceFileFromAssetsCatalog(source_file.value(), &assets_catalog)) {
+        assets_catalog_sources.push_back(assets_catalog);
+        assets_catalog_deps.push_back(target);
+      } else {
+        file_rule_sources.push_back(source_file);
+      }
+    }
+
+    if (!file_rule_sources.empty()) {
+      DCHECK_EQ(target->action_values().outputs().list().size(), 1u);
+      file_rules_.push_back(
+          BundleFileRule(target, file_rule_sources,
+                         target->action_values().outputs().list()[0]));
+    }
+  }
+
+  assets_catalog_deps_.insert(assets_catalog_deps_.end(),
+                              assets_catalog_deps.begin(),
+                              assets_catalog_deps.end());
+  assets_catalog_sources_.insert(assets_catalog_sources_.end(),
+                                 assets_catalog_sources.begin(),
+                                 assets_catalog_sources.end());
+
+  GetSourceFiles(&owning_target->sources());
+}
+
+void BundleData::GetSourceFiles(SourceFiles* sources) const {
+  for (const BundleFileRule& file_rule : file_rules_) {
+    sources->insert(sources->end(), file_rule.sources().begin(),
+                    file_rule.sources().end());
+  }
+  sources->insert(sources->end(), assets_catalog_sources_.begin(),
+                  assets_catalog_sources_.end());
+  if (!code_signing_script_.is_null()) {
+    sources->insert(sources->end(), code_signing_sources_.begin(),
+                    code_signing_sources_.end());
+  }
+}
+
+bool BundleData::GetOutputFiles(const Settings* settings,
+                                const Target* target,
+                                OutputFiles* outputs,
+                                Err* err) const {
+  SourceFiles outputs_as_sources;
+  if (!GetOutputsAsSourceFiles(settings, target, &outputs_as_sources, err))
+    return false;
+  for (const SourceFile& source_file : outputs_as_sources)
+    outputs->push_back(OutputFile(settings->build_settings(), source_file));
+  return true;
+}
+
+bool BundleData::GetOutputsAsSourceFiles(const Settings* settings,
+                                         const Target* target,
+                                         SourceFiles* outputs_as_source,
+                                         Err* err) const {
+  for (const BundleFileRule& file_rule : file_rules_) {
+    for (const SourceFile& source : file_rule.sources()) {
+      SourceFile expanded_source_file;
+      if (!file_rule.ApplyPatternToSource(settings, target, *this, source,
+                                          &expanded_source_file, err))
+        return false;
+      outputs_as_source->push_back(expanded_source_file);
+    }
+  }
+
+  if (!assets_catalog_sources_.empty())
+    outputs_as_source->push_back(GetCompiledAssetCatalogPath());
+
+  if (!partial_info_plist_.is_null())
+    outputs_as_source->push_back(partial_info_plist_);
+
+  if (!code_signing_script_.is_null()) {
+    std::vector<SourceFile> code_signing_output_files;
+    SubstitutionWriter::GetListAsSourceFiles(code_signing_outputs_,
+                                             &code_signing_output_files);
+    outputs_as_source->insert(outputs_as_source->end(),
+                              code_signing_output_files.begin(),
+                              code_signing_output_files.end());
+  }
+
+  if (!root_dir_.is_null())
+    outputs_as_source->push_back(GetBundleRootDirOutput(settings));
+
+  return true;
+}
+
+SourceFile BundleData::GetCompiledAssetCatalogPath() const {
+  DCHECK(!assets_catalog_sources_.empty());
+  std::string assets_car_path = resources_dir_.value() + "/Assets.car";
+  return SourceFile(std::move(assets_car_path));
+}
+
+SourceFile BundleData::GetBundleRootDirOutput(const Settings* settings) const {
+  std::string root_dir_value = root_dir().value();
+  size_t last_separator = root_dir_value.rfind('/');
+  if (last_separator != std::string::npos)
+    root_dir_value = root_dir_value.substr(0, last_separator);
+
+  return SourceFile(std::move(root_dir_value));
+}
+
+SourceDir BundleData::GetBundleRootDirOutputAsDir(
+    const Settings* settings) const {
+  return SourceDir(GetBundleRootDirOutput(settings).value());
+}
+
+SourceDir BundleData::GetBundleDir(const Settings* settings) const{
+  return GetBundleRootDirOutput(settings).GetDir();
+}
diff --git a/src/gn/bundle_data.h b/src/gn/bundle_data.h
new file mode 100644 (file)
index 0000000..3c4feb1
--- /dev/null
@@ -0,0 +1,214 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUNDLE_DATA_H_
+#define TOOLS_GN_BUNDLE_DATA_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "gn/action_values.h"
+#include "gn/bundle_file_rule.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/substitution_list.h"
+#include "gn/unique_vector.h"
+
+class LabelPattern;
+class OutputFile;
+class Settings;
+class Target;
+
+// BundleData holds the information required by "create_bundle" target.
+class BundleData {
+ public:
+  using UniqueTargets = UniqueVector<const Target*>;
+  using SourceFiles = std::vector<SourceFile>;
+  using OutputFiles = std::vector<OutputFile>;
+  using BundleFileRules = std::vector<BundleFileRule>;
+
+  BundleData();
+  ~BundleData();
+
+  // Adds a bundle_data target to the recursive collection of all bundle_data
+  // that the target depends on.
+  void AddBundleData(const Target* target);
+
+  // Called upon resolution of the target owning this instance of BundleData.
+  // |owning_target| is the owning target.
+  void OnTargetResolved(Target* owning_target);
+
+  // Returns the list of inputs.
+  void GetSourceFiles(SourceFiles* sources) const;
+
+  // Returns the list of outputs.
+  bool GetOutputFiles(const Settings* settings,
+                      const Target* target,
+                      OutputFiles* outputs,
+                      Err* err) const;
+
+  // Returns the list of outputs as SourceFile.
+  bool GetOutputsAsSourceFiles(const Settings* settings,
+                               const Target* target,
+                               SourceFiles* outputs_as_source,
+                               Err* err) const;
+
+  // Returns the path to the compiled asset catalog. Only valid if
+  // assets_catalog_sources() is not empty.
+  SourceFile GetCompiledAssetCatalogPath() const;
+
+  // Returns the path to the top-level directory of the bundle. This is
+  // based on root_dir(), but since that can be Bundle.app/Contents/ or
+  // any other subpath, this is just the most top-level directory (e.g.,
+  // just Bundle.app/).
+  //
+  // Note that this is a SourceFile instead of a SourceDir. This is because
+  // the output of a create_bundle rule is a single logical unit, even though
+  // it is really a directory containing many outputs. This allows other
+  // targets to treat the bundle as a single unit, rather than a collection
+  // of its contents.
+  SourceFile GetBundleRootDirOutput(const Settings* settings) const;
+
+  // Performs GetBundleRootDirOutput but returns the result as a directory.
+  SourceDir GetBundleRootDirOutputAsDir(const Settings* settings) const;
+
+  // Returns directory where bundle is
+  SourceDir GetBundleDir(const Settings* settings) const;
+
+  // Returns the list of inputs for the compilation of the asset catalog.
+  SourceFiles& assets_catalog_sources() { return assets_catalog_sources_; }
+  const SourceFiles& assets_catalog_sources() const {
+    return assets_catalog_sources_;
+  }
+
+  // Returns the list of dependencies for the compilation of the asset catalog.
+  std::vector<const Target*> assets_catalog_deps() const {
+    return assets_catalog_deps_;
+  }
+
+  BundleFileRules& file_rules() { return file_rules_; }
+  const BundleFileRules& file_rules() const { return file_rules_; }
+
+  SourceDir& root_dir() { return root_dir_; }
+  const SourceDir& root_dir() const { return root_dir_; }
+
+  SourceDir& contents_dir() { return contents_dir_; }
+  const SourceDir& contents_dir() const { return contents_dir_; }
+
+  SourceDir& resources_dir() { return resources_dir_; }
+  const SourceDir& resources_dir() const { return resources_dir_; }
+
+  SourceDir& executable_dir() { return executable_dir_; }
+  const SourceDir& executable_dir() const { return executable_dir_; }
+
+  std::map<std::string, std::string>& xcode_extra_attributes() {
+    return xcode_extra_attributes_;
+  }
+  const std::map<std::string, std::string>& xcode_extra_attributes() const {
+    return xcode_extra_attributes_;
+  }
+
+  std::string& product_type() { return product_type_; }
+  const std::string& product_type() const { return product_type_; }
+
+  std::string& xcode_test_application_name() {
+    return xcode_test_application_name_;
+  }
+  const std::string& xcode_test_application_name() const {
+    return xcode_test_application_name_;
+  }
+
+  void set_partial_info_plist(const SourceFile& partial_info_plist) {
+    partial_info_plist_ = partial_info_plist;
+  }
+  const SourceFile& partial_info_plist() const { return partial_info_plist_; }
+
+  void set_code_signing_script(const SourceFile& script_file) {
+    code_signing_script_ = script_file;
+  }
+  const SourceFile& code_signing_script() const { return code_signing_script_; }
+
+  std::vector<SourceFile>& code_signing_sources() {
+    return code_signing_sources_;
+  }
+  const std::vector<SourceFile>& code_signing_sources() const {
+    return code_signing_sources_;
+  }
+
+  SubstitutionList& code_signing_outputs() { return code_signing_outputs_; }
+  const SubstitutionList& code_signing_outputs() const {
+    return code_signing_outputs_;
+  }
+
+  SubstitutionList& code_signing_args() { return code_signing_args_; }
+  const SubstitutionList& code_signing_args() const {
+    return code_signing_args_;
+  }
+
+  std::vector<LabelPattern>& bundle_deps_filter() {
+    return bundle_deps_filter_;
+  }
+  const std::vector<LabelPattern>& bundle_deps_filter() const {
+    return bundle_deps_filter_;
+  }
+
+  SubstitutionList& xcasset_compiler_flags() {
+    return xcasset_compiler_flags_;
+  }
+  const SubstitutionList& xcasset_compiler_flags() const {
+    return xcasset_compiler_flags_;
+  }
+
+  // Recursive collection of all bundle_data that the target depends on.
+  const UniqueTargets& bundle_deps() const { return bundle_deps_; }
+
+  // Returns whether the bundle is a framework bundle.
+  bool is_framework() const {
+    return product_type_ == "com.apple.product-type.framework";
+  }
+
+ private:
+  SourceFiles assets_catalog_sources_;
+  std::vector<const Target*> assets_catalog_deps_;
+  BundleFileRules file_rules_;
+  UniqueTargets bundle_deps_;
+  std::vector<LabelPattern> bundle_deps_filter_;
+
+  // All those values are subdirectories relative to root_build_dir, and apart
+  // from root_dir_, they are either equal to root_dir_ or subdirectories of it.
+  SourceDir root_dir_;
+  SourceDir contents_dir_;
+  SourceDir resources_dir_;
+  SourceDir executable_dir_;
+
+  // The specified attributes will append to the build settings of the generated
+  // Xcode target.
+  std::map<std::string, std::string> xcode_extra_attributes_;
+
+  // This is the target type as known to Xcode. This is only used to generate
+  // the Xcode project file when using --ide=xcode.
+  std::string product_type_;
+
+  // Each Xcode unit test or ui test target must have a test application target,
+  // and this value corresponds to the target name. This is only used to
+  // generate the Xcode project when using --ide=xcode.
+  std::string xcode_test_application_name_;
+
+  // Path to the partial Info.plist generated by the asset catalog compiler
+  // (corresponds to {{bundle_partial_info_plist}} expansion).
+  SourceFile partial_info_plist_;
+
+  // Holds the values (script name, sources, outputs, script arguments) for the
+  // code signing step if defined.
+  SourceFile code_signing_script_;
+  std::vector<SourceFile> code_signing_sources_;
+  SubstitutionList code_signing_outputs_;
+  SubstitutionList code_signing_args_;
+  SubstitutionList xcasset_compiler_flags_;
+
+  DISALLOW_COPY_AND_ASSIGN(BundleData);
+};
+
+#endif  // TOOLS_GN_BUNDLE_DATA_H_
diff --git a/src/gn/bundle_data_target_generator.cc b/src/gn/bundle_data_target_generator.cc
new file mode 100644 (file)
index 0000000..0246cff
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/bundle_data_target_generator.h"
+
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/substitution_type.h"
+#include "gn/target.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+
+BundleDataTargetGenerator::BundleDataTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err) {}
+
+BundleDataTargetGenerator::~BundleDataTargetGenerator() = default;
+
+void BundleDataTargetGenerator::DoRun() {
+  target_->set_output_type(Target::BUNDLE_DATA);
+
+  if (!FillSources())
+    return;
+  if (!FillOutputs())
+    return;
+
+  if (target_->sources().empty()) {
+    *err_ = Err(function_call_,
+                "Empty sources for bundle_data target."
+                "You have to specify at least one file in the \"sources\".");
+    return;
+  }
+  if (target_->action_values().outputs().list().size() != 1) {
+    *err_ = Err(
+        function_call_, "Target bundle_data must have exactly one output.",
+        "You must specify exactly one value in the \"output\" array for the"
+        "destination\ninto the generated bundle (see \"gn help bundle_data\"). "
+        "If there are multiple\nsources to copy, use source expansion (see "
+        "\"gn help source_expansion\").");
+    return;
+  }
+}
+
+bool BundleDataTargetGenerator::FillOutputs() {
+  const Value* value = scope_->GetValue(variables::kOutputs, true);
+  if (!value)
+    return true;
+
+  SubstitutionList& outputs = target_->action_values().outputs();
+  if (!outputs.Parse(*value, err_))
+    return false;
+
+  // Check the substitutions used are valid for this purpose.
+  for (const Substitution* type : outputs.required_types()) {
+    if (!IsValidBundleDataSubstitution(type)) {
+      *err_ = Err(value->origin(), "Invalid substitution type.",
+                  "The substitution " + std::string(type->name) +
+                      " isn't valid for something\n"
+                      "operating on a bundle_data file such as this.");
+      return false;
+    }
+  }
+
+  // Validate that outputs are in the bundle.
+  CHECK(outputs.list().size() == value->list_value().size());
+  for (size_t i = 0; i < outputs.list().size(); i++) {
+    if (!EnsureSubstitutionIsInBundleDir(outputs.list()[i],
+                                         value->list_value()[i]))
+      return false;
+  }
+
+  return true;
+}
+
+bool BundleDataTargetGenerator::EnsureSubstitutionIsInBundleDir(
+    const SubstitutionPattern& pattern,
+    const Value& original_value) {
+  if (pattern.ranges().empty()) {
+    // Pattern is empty, error out (this prevents weirdness below).
+    *err_ = Err(original_value, "This has an empty value in it.");
+    return false;
+  }
+
+  if (SubstitutionIsInBundleDir(pattern.ranges()[0].type))
+    return true;
+
+  *err_ = Err(original_value, "File is not inside bundle directory.",
+              "The given file should be in the output directory. Normally you\n"
+              "would specify {{bundle_resources_dir}} or such substitution.");
+  return false;
+}
diff --git a/src/gn/bundle_data_target_generator.h b/src/gn/bundle_data_target_generator.h
new file mode 100644 (file)
index 0000000..05ea820
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
+#define TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target_generator.h"
+
+// Populates a Target with the values from a bundle_data rule.
+class BundleDataTargetGenerator : public TargetGenerator {
+ public:
+  BundleDataTargetGenerator(Target* target,
+                            Scope* scope,
+                            const FunctionCallNode* function_call,
+                            Err* err);
+  ~BundleDataTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillOutputs();
+
+  bool EnsureSubstitutionIsInBundleDir(const SubstitutionPattern& pattern,
+                                       const Value& original_value);
+
+  DISALLOW_COPY_AND_ASSIGN(BundleDataTargetGenerator);
+};
+
+#endif  // TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
diff --git a/src/gn/bundle_file_rule.cc b/src/gn/bundle_file_rule.cc
new file mode 100644 (file)
index 0000000..719e8aa
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/bundle_file_rule.h"
+
+#include "base/strings/stringprintf.h"
+#include "gn/output_file.h"
+#include "gn/settings.h"
+#include "gn/substitution_pattern.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+
+namespace {
+
+Err ErrMissingPropertyForExpansion(const Settings* settings,
+                                   const Target* target,
+                                   const BundleFileRule* bundle_file_rule,
+                                   const char* property_name) {
+  std::string label = bundle_file_rule->target()->label().GetUserVisibleName(
+      settings->default_toolchain_label());
+
+  return Err(target->defined_from(),
+             base::StringPrintf("Property %s is required.", property_name),
+             base::StringPrintf(
+                 "In order to expand {{%s}} in %s, the "
+                 "property needs to be defined in the create_bundle target.",
+                 property_name, label.c_str()));
+}
+
+}  // namespace
+
+BundleFileRule::BundleFileRule(const Target* bundle_data_target,
+                               const std::vector<SourceFile> sources,
+                               const SubstitutionPattern& pattern)
+    : target_(bundle_data_target), sources_(sources), pattern_(pattern) {
+  // target_ may be null during testing.
+  DCHECK(!target_ || target_->output_type() == Target::BUNDLE_DATA);
+}
+
+BundleFileRule::BundleFileRule(const BundleFileRule& other) = default;
+
+BundleFileRule::~BundleFileRule() = default;
+
+bool BundleFileRule::ApplyPatternToSource(const Settings* settings,
+                                          const Target* target,
+                                          const BundleData& bundle_data,
+                                          const SourceFile& source_file,
+                                          SourceFile* expanded_source_file,
+                                          Err* err) const {
+  std::string output_path;
+  for (const auto& subrange : pattern_.ranges()) {
+    if (subrange.type == &SubstitutionLiteral) {
+      output_path.append(subrange.literal);
+    } else if (subrange.type == &SubstitutionBundleRootDir) {
+      if (bundle_data.root_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleRootDir);
+        return false;
+      }
+      output_path.append(bundle_data.root_dir().value());
+    } else if (subrange.type == &SubstitutionBundleContentsDir) {
+      if (bundle_data.contents_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleContentsDir);
+        return false;
+      }
+      output_path.append(bundle_data.contents_dir().value());
+    } else if (subrange.type == &SubstitutionBundleResourcesDir) {
+      if (bundle_data.resources_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleResourcesDir);
+        return false;
+      }
+      output_path.append(bundle_data.resources_dir().value());
+    } else if (subrange.type == &SubstitutionBundleExecutableDir) {
+      if (bundle_data.executable_dir().is_null()) {
+        *err = ErrMissingPropertyForExpansion(settings, target, this,
+                                              variables::kBundleExecutableDir);
+        return false;
+      }
+      output_path.append(bundle_data.executable_dir().value());
+    } else {
+      output_path.append(SubstitutionWriter::GetSourceSubstitution(
+          target_, target_->settings(), source_file, subrange.type,
+          SubstitutionWriter::OUTPUT_ABSOLUTE, SourceDir()));
+    }
+  }
+  *expanded_source_file = SourceFile(std::move(output_path));
+  return true;
+}
+
+bool BundleFileRule::ApplyPatternToSourceAsOutputFile(
+    const Settings* settings,
+    const Target* target,
+    const BundleData& bundle_data,
+    const SourceFile& source_file,
+    OutputFile* expanded_output_file,
+    Err* err) const {
+  SourceFile expanded_source_file;
+  if (!ApplyPatternToSource(settings, target, bundle_data, source_file,
+                            &expanded_source_file, err)) {
+    return false;
+  }
+
+  *expanded_output_file =
+      OutputFile(settings->build_settings(), expanded_source_file);
+  return true;
+}
diff --git a/src/gn/bundle_file_rule.h b/src/gn/bundle_file_rule.h
new file mode 100644 (file)
index 0000000..acc824a
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUNDLE_FILE_RULE_H_
+#define TOOLS_GN_BUNDLE_FILE_RULE_H_
+
+#include <vector>
+
+#include "gn/source_file.h"
+#include "gn/substitution_pattern.h"
+
+class BundleData;
+class Settings;
+class SourceFile;
+class Target;
+class OutputFile;
+
+// BundleFileRule contains the information found in a "bundle_data" target.
+class BundleFileRule {
+ public:
+  BundleFileRule(const Target* bundle_data_target,
+                 const std::vector<SourceFile> sources,
+                 const SubstitutionPattern& pattern);
+  BundleFileRule(const BundleFileRule& other);
+  ~BundleFileRule();
+
+  // Applies the substitution pattern to a source file, returning the result
+  // as either a SourceFile or an OutputFile.
+  bool ApplyPatternToSource(const Settings* settings,
+                            const Target* target,
+                            const BundleData& bundle_data,
+                            const SourceFile& source_file,
+                            SourceFile* expanded_source_file,
+                            Err* err) const;
+  bool ApplyPatternToSourceAsOutputFile(const Settings* settings,
+                                        const Target* target,
+                                        const BundleData& bundle_data,
+                                        const SourceFile& source_file,
+                                        OutputFile* expanded_output_file,
+                                        Err* err) const;
+
+  // Returns the associated target (of type Target::BUNDLE_DATA). May be
+  // null during testing.
+  const Target* target() const { return target_; }
+
+  // Returns the list of SourceFiles.
+  const std::vector<SourceFile>& sources() const { return sources_; }
+
+ private:
+  const Target* target_;
+  std::vector<SourceFile> sources_;
+  SubstitutionPattern pattern_;
+};
+
+#endif  // TOOLS_GN_BUNDLE_FILE_RULE_H_
diff --git a/src/gn/c_include_iterator.cc b/src/gn/c_include_iterator.cc
new file mode 100644 (file)
index 0000000..f8626e1
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/c_include_iterator.h"
+
+#include <iterator>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "gn/input_file.h"
+#include "gn/location.h"
+
+namespace {
+
+enum IncludeType {
+  INCLUDE_NONE,
+  INCLUDE_SYSTEM,  // #include <...>
+  INCLUDE_USER     // #include "..."
+};
+
+// Returns a new string piece referencing the same buffer as the argument, but
+// with leading space trimmed. This only checks for space and tab characters
+// since we're dealing with lines in C source files.
+std::string_view TrimLeadingWhitespace(const std::string_view& str) {
+  size_t new_begin = 0;
+  while (new_begin < str.size() &&
+         (str[new_begin] == ' ' || str[new_begin] == '\t'))
+    new_begin++;
+  return str.substr(new_begin);
+}
+
+// We don't want to count comment lines and preprocessor lines toward our
+// "max lines to look at before giving up" since the beginnings of some files
+// may have a lot of comments.
+//
+// We only handle C-style "//" comments since this is the normal commenting
+// style used in Chrome, and do so pretty stupidly. We don't want to write a
+// full C++ parser here, we're just trying to get a good heuristic for checking
+// the file.
+//
+// We assume the line has leading whitespace trimmed. We also assume that empty
+// lines have already been filtered out.
+bool ShouldCountTowardNonIncludeLines(const std::string_view& line) {
+  if (base::StartsWith(line, "//", base::CompareCase::SENSITIVE))
+    return false;  // Don't count comments.
+  if (base::StartsWith(line, "/*", base::CompareCase::SENSITIVE) ||
+      base::StartsWith(line, " *", base::CompareCase::SENSITIVE))
+    return false;  // C-style comment blocks with stars along the left side.
+  if (base::StartsWith(line, "#", base::CompareCase::SENSITIVE))
+    return false;  // Don't count preprocessor.
+  if (base::ContainsOnlyChars(line, base::kWhitespaceASCII))
+    return false;  // Don't count whitespace lines.
+  return true;     // Count everything else.
+}
+
+// Given a line, checks to see if it looks like an include or import and
+// extract the path. The type of include is returned. Returns INCLUDE_NONE on
+// error or if this is not an include line.
+//
+// The 1-based character number on the line that the include was found at
+// will be filled into *begin_char.
+IncludeType ExtractInclude(const std::string_view& line,
+                           std::string_view* path,
+                           int* begin_char) {
+  static const char kInclude[] = "include";
+  static const size_t kIncludeLen = std::size(kInclude) - 1;  // No null.
+  static const char kImport[] = "import";
+  static const size_t kImportLen = std::size(kImport) - 1;  // No null.
+
+  std::string_view trimmed = TrimLeadingWhitespace(line);
+  if (trimmed.empty())
+    return INCLUDE_NONE;
+
+  if (trimmed[0] != '#')
+    return INCLUDE_NONE;
+
+  trimmed = TrimLeadingWhitespace(trimmed.substr(1));
+
+  std::string_view contents;
+  if (base::StartsWith(trimmed, std::string_view(kInclude, kIncludeLen),
+                       base::CompareCase::SENSITIVE))
+    contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen));
+  else if (base::StartsWith(trimmed, std::string_view(kImport, kImportLen),
+                            base::CompareCase::SENSITIVE))
+    contents = TrimLeadingWhitespace(trimmed.substr(kImportLen));
+
+  if (contents.empty())
+    return INCLUDE_NONE;
+
+  IncludeType type = INCLUDE_NONE;
+  char terminating_char = 0;
+  if (contents[0] == '"') {
+    type = INCLUDE_USER;
+    terminating_char = '"';
+  } else if (contents[0] == '<') {
+    type = INCLUDE_SYSTEM;
+    terminating_char = '>';
+  } else {
+    return INCLUDE_NONE;
+  }
+
+  // Count everything to next "/> as the contents.
+  size_t terminator_index = contents.find(terminating_char, 1);
+  if (terminator_index == std::string_view::npos)
+    return INCLUDE_NONE;
+
+  *path = contents.substr(1, terminator_index - 1);
+  // Note: one based so we do "+ 1".
+  *begin_char = static_cast<int>(path->data() - line.data()) + 1;
+  return type;
+}
+
+// Returns true if this line has a "nogncheck" comment associated with it.
+bool HasNoCheckAnnotation(const std::string_view& line) {
+  return line.find("nogncheck") != std::string_view::npos;
+}
+
+}  // namespace
+
+const int CIncludeIterator::kMaxNonIncludeLines = 10;
+
+CIncludeIterator::CIncludeIterator(const InputFile* input)
+    : input_file_(input), file_(input->contents()) {}
+
+CIncludeIterator::~CIncludeIterator() = default;
+
+bool CIncludeIterator::GetNextIncludeString(
+    IncludeStringWithLocation* include) {
+  std::string_view line;
+  int cur_line_number = 0;
+  while (lines_since_last_include_ <= kMaxNonIncludeLines &&
+         GetNextLine(&line, &cur_line_number)) {
+    std::string_view include_contents;
+    int begin_char;
+    IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
+    if (HasNoCheckAnnotation(line))
+      continue;
+    if (type != INCLUDE_NONE) {
+      include->contents = include_contents;
+      include->location = LocationRange(
+          Location(input_file_, cur_line_number, begin_char,
+                   -1 /* TODO(scottmg): Is this important? */),
+          Location(input_file_, cur_line_number,
+                   begin_char + static_cast<int>(include_contents.size()),
+                   -1 /* TODO(scottmg): Is this important? */));
+      include->system_style_include = (type == INCLUDE_SYSTEM);
+
+      lines_since_last_include_ = 0;
+      return true;
+    }
+
+    if (ShouldCountTowardNonIncludeLines(line))
+      lines_since_last_include_++;
+  }
+  return false;
+}
+
+bool CIncludeIterator::GetNextLine(std::string_view* line, int* line_number) {
+  if (offset_ == file_.size())
+    return false;
+
+  size_t begin = offset_;
+  while (offset_ < file_.size() && file_[offset_] != '\n')
+    offset_++;
+  line_number_++;
+
+  *line = file_.substr(begin, offset_ - begin);
+  *line_number = line_number_;
+
+  // If we didn't hit EOF, skip past the newline for the next one.
+  if (offset_ < file_.size())
+    offset_++;
+  return true;
+}
diff --git a/src/gn/c_include_iterator.h b/src/gn/c_include_iterator.h
new file mode 100644 (file)
index 0000000..325d57e
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_C_INCLUDE_ITERATOR_H_
+#define TOOLS_GN_C_INCLUDE_ITERATOR_H_
+
+#include <stddef.h>
+
+#include <string_view>
+
+#include "base/macros.h"
+#include "gn/location.h"
+
+class InputFile;
+
+struct IncludeStringWithLocation {
+  std::string_view contents;
+  LocationRange location;
+  bool system_style_include = false;
+};
+
+// Iterates through #includes in C source and header files.
+class CIncludeIterator {
+ public:
+  // The InputFile pointed to must outlive this class.
+  explicit CIncludeIterator(const InputFile* input);
+  ~CIncludeIterator();
+
+  // Fills in the string with the contents of the next include, and the
+  // location with where it came from, and returns true, or returns false if
+  // there are no more includes.
+  bool GetNextIncludeString(IncludeStringWithLocation* include);
+
+  // Maximum numbef of non-includes we'll tolerate before giving up. This does
+  // not count comments or preprocessor.
+  static const int kMaxNonIncludeLines;
+
+ private:
+  // Returns false on EOF, otherwise fills in the given line and the one-based
+  // line number into *line_number;
+  bool GetNextLine(std::string_view* line, int* line_number);
+
+  const InputFile* input_file_;
+
+  // This just points into input_file_.contents() for convenience.
+  std::string_view file_;
+
+  // 0-based offset into the file.
+  size_t offset_ = 0;
+
+  int line_number_ = 0;  // One-based. Indicates the last line we read.
+
+  // Number of lines we've processed since seeing the last include (or the
+  // beginning of the file) with some exceptions.
+  int lines_since_last_include_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(CIncludeIterator);
+};
+
+#endif  // TOOLS_GN_C_INCLUDE_ITERATOR_H_
diff --git a/src/gn/c_include_iterator_unittest.cc b/src/gn/c_include_iterator_unittest.cc
new file mode 100644 (file)
index 0000000..b162ced
--- /dev/null
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/c_include_iterator.h"
+#include "gn/input_file.h"
+#include "gn/location.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool RangeIs(const LocationRange& range,
+             int line,
+             int begin_char,
+             int end_char) {
+  return range.begin().line_number() == line &&
+         range.end().line_number() == line &&
+         range.begin().column_number() == begin_char &&
+         range.end().column_number() == end_char;
+}
+
+}  // namespace
+
+TEST(CIncludeIterator, Basic) {
+  std::string buffer;
+  buffer.append("// Some comment\n");
+  buffer.append("\n");
+  buffer.append("#include \"foo/bar.h\"\n");
+  buffer.append("\n");
+  buffer.append("#include <stdio.h>\n");
+  buffer.append("\n");
+  buffer.append(" #include \"foo/baz.h\"\n");  // Leading whitespace
+  buffer.append("#include \"la/deda.h\"\n");
+  // Line annotated with "// nogncheck"
+  buffer.append("#include \"should_be_skipped.h\"  // nogncheck\n");
+  buffer.append("#import \"weird_mac_import.h\"\n");
+  buffer.append("\n");
+  buffer.append("void SomeCode() {\n");
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  CIncludeIterator iter(&file);
+
+  IncludeStringWithLocation include;
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 3, 11, 20)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
+
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("stdio.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 5, 11, 18)) << include.location.begin().Describe(true);
+  EXPECT_TRUE(include.system_style_include);
+
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/baz.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 7, 12, 21)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
+
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("la/deda.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 8, 11, 20)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
+
+  // The line annotated with "nogncheck" should be skipped.
+
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("weird_mac_import.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 10, 10, 28)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
+
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
+}
+
+// Tests that we don't search for includes indefinitely.
+TEST(CIncludeIterator, GiveUp) {
+  std::string buffer;
+  for (size_t i = 0; i < 1000; i++)
+    buffer.append("x\n");
+  buffer.append("#include \"foo/bar.h\"\n");
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  IncludeStringWithLocation include;
+
+  CIncludeIterator iter(&file);
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
+  EXPECT_TRUE(include.contents.empty());
+}
+
+// Don't count blank lines, comments, and preprocessor when giving up.
+TEST(CIncludeIterator, DontGiveUp) {
+  std::string buffer;
+  for (size_t i = 0; i < 1000; i++)
+    buffer.push_back('\n');
+  for (size_t i = 0; i < 1000; i++)
+    buffer.append("// comment\n");
+  for (size_t i = 0; i < 1000; i++)
+    buffer.append("#preproc\n");
+  buffer.append("#include \"foo/bar.h\"\n");
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  IncludeStringWithLocation include;
+
+  CIncludeIterator iter(&file);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
+}
+
+// Tests that we'll tolerate some small numbers of non-includes interspersed
+// with real includes.
+TEST(CIncludeIterator, TolerateNonIncludes) {
+  const size_t kSkip = CIncludeIterator::kMaxNonIncludeLines - 2;
+  const size_t kGroupCount = 100;
+
+  std::string include_str("foo/bar.h");
+
+  // Allow a series of includes with blanks in between.
+  std::string buffer;
+  for (size_t group = 0; group < kGroupCount; group++) {
+    for (size_t i = 0; i < kSkip; i++)
+      buffer.append("foo\n");
+    buffer.append("#include \"" + include_str + "\"\n");
+  }
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  IncludeStringWithLocation include;
+
+  CIncludeIterator iter(&file);
+  for (size_t group = 0; group < kGroupCount; group++) {
+    EXPECT_TRUE(iter.GetNextIncludeString(&include));
+    EXPECT_EQ(include_str, std::string(include.contents));
+  }
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
+}
+
+// Tests that comments of the form
+//    /*
+//     *
+//     */
+// are not counted toward the non-include line count.
+TEST(CIncludeIterator, CStyleComments) {
+  std::string buffer("/*");
+  for (size_t i = 0; i < 1000; i++)
+    buffer.append(" *\n");
+  buffer.append(" */\n\n");
+  buffer.append("#include \"foo/bar.h\"\n");
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  IncludeStringWithLocation include;
+
+  CIncludeIterator iter(&file);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
+}
+
+// Tests that spaces between the hash and directive are ignored.
+TEST(CIncludeIterator, SpacesAfterHash) {
+  std::string buffer("#     include \"foo/bar.h\"\n");
+
+  InputFile file(SourceFile("//foo.cc"));
+  file.SetContents(buffer);
+
+  IncludeStringWithLocation include;
+
+  CIncludeIterator iter(&file);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
+
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
+}
diff --git a/src/gn/c_substitution_type.cc b/src/gn/c_substitution_type.cc
new file mode 100644 (file)
index 0000000..ea47ce9
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/c_substitution_type.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "gn/err.h"
+
+const SubstitutionTypes CSubstitutions = {
+    &CSubstitutionAsmFlags,
+    &CSubstitutionCFlags,
+    &CSubstitutionCFlagsC,
+    &CSubstitutionCFlagsCc,
+    &CSubstitutionCFlagsObjC,
+    &CSubstitutionCFlagsObjCc,
+    &CSubstitutionDefines,
+    &CSubstitutionFrameworkDirs,
+    &CSubstitutionIncludeDirs,
+    &CSubstitutionModuleDeps,
+    &CSubstitutionModuleDepsNoSelf,
+    &CSubstitutionSwiftModules,
+
+    &CSubstitutionLinkerInputs,
+    &CSubstitutionLinkerInputsNewline,
+    &CSubstitutionLdFlags,
+    &CSubstitutionLibs,
+    &CSubstitutionSoLibs,
+    &CSubstitutionFrameworks,
+    &CSubstitutionRlibs,
+
+    &CSubstitutionArFlags,
+
+    &CSubstitutionSwiftModuleName,
+    &CSubstitutionSwiftBridgeHeader,
+    &CSubstitutionSwiftModuleDirs,
+    &CSubstitutionSwiftFlags,
+};
+
+// Valid for compiler tools.
+const Substitution CSubstitutionAsmFlags = {"{{asmflags}}", "asmflags"};
+const Substitution CSubstitutionCFlags = {"{{cflags}}", "cflags"};
+const Substitution CSubstitutionCFlagsC = {"{{cflags_c}}", "cflags_c"};
+const Substitution CSubstitutionCFlagsCc = {"{{cflags_cc}}", "cflags_cc"};
+const Substitution CSubstitutionCFlagsObjC = {"{{cflags_objc}}", "cflags_objc"};
+const Substitution CSubstitutionCFlagsObjCc = {"{{cflags_objcc}}",
+                                              "cflags_objcc"};
+const Substitution CSubstitutionDefines = {"{{defines}}", "defines"};
+const Substitution CSubstitutionFrameworkDirs = {"{{framework_dirs}}",
+                                                 "framework_dirs"};
+const Substitution CSubstitutionIncludeDirs = {"{{include_dirs}}",
+                                              "include_dirs"};
+const Substitution CSubstitutionModuleDeps = {"{{module_deps}}", "module_deps"};
+const Substitution CSubstitutionModuleDepsNoSelf = {"{{module_deps_no_self}}",
+                                                    "module_deps_no_self"};
+
+// Valid for linker tools.
+const Substitution CSubstitutionLinkerInputs = {"{{inputs}}", "in"};
+const Substitution CSubstitutionLinkerInputsNewline = {"{{inputs_newline}}",
+                                                       "in_newline"};
+const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
+const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
+const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
+const Substitution CSubstitutionRlibs = {"{{rlibs}}", "rlibs"};
+const Substitution CSubstitutionFrameworks = {"{{frameworks}}", "frameworks"};
+const Substitution CSubstitutionSwiftModules = {"{{swiftmodules}}",
+                                                "swiftmodules"};
+
+// Valid for alink only.
+const Substitution CSubstitutionArFlags = {"{{arflags}}", "arflags"};
+
+// Valid for swift only.
+const Substitution CSubstitutionSwiftModuleName = {"{{module_name}}",
+                                                   "module_name"};
+const Substitution CSubstitutionSwiftBridgeHeader = {"{{bridge_header}}",
+                                                     "bridge_header"};
+const Substitution CSubstitutionSwiftModuleDirs = {"{{module_dirs}}",
+                                                   "module_dirs"};
+const Substitution CSubstitutionSwiftFlags = {"{{swiftflags}}", "swiftflags"};
+
+bool IsValidCompilerSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
+         type == &SubstitutionSource || type == &CSubstitutionAsmFlags ||
+         type == &CSubstitutionCFlags || type == &CSubstitutionCFlagsC ||
+         type == &CSubstitutionCFlagsCc || type == &CSubstitutionCFlagsObjC ||
+         type == &CSubstitutionCFlagsObjCc || type == &CSubstitutionDefines ||
+         type == &CSubstitutionFrameworkDirs ||
+         type == &CSubstitutionIncludeDirs ||
+         type == &CSubstitutionModuleDeps ||
+         type == &CSubstitutionModuleDepsNoSelf;
+}
+
+bool IsValidCompilerOutputsSubstitution(const Substitution* type) {
+  // All tool types except "output" (which would be infinitely recursive).
+  return (IsValidToolSubstitution(type) && type != &SubstitutionOutput) ||
+         IsValidSourceSubstitution(type);
+}
+
+bool IsValidSwiftCompilerSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) ||
+         type == &CSubstitutionSwiftModuleName ||
+         type == &CSubstitutionLinkerInputs ||
+         type == &CSubstitutionIncludeDirs ||
+         type == &CSubstitutionSwiftBridgeHeader ||
+         type == &CSubstitutionSwiftModuleDirs ||
+         type == &CSubstitutionSwiftFlags || type == &CSubstitutionDefines;
+}
+
+bool IsValidSwiftCompilerOutputsSubstitution(const Substitution* type) {
+  return (IsValidSwiftCompilerSubstitution(type) &&
+          type != &SubstitutionOutput) ||
+         IsValidSourceSubstitution(type);
+}
+
+bool IsValidLinkerSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
+         type == &CSubstitutionLinkerInputs ||
+         type == &CSubstitutionLinkerInputsNewline ||
+         type == &CSubstitutionLdFlags || type == &CSubstitutionLibs ||
+         type == &CSubstitutionSoLibs || type == &CSubstitutionFrameworks ||
+         type == &CSubstitutionRlibs || type == &CSubstitutionSwiftModules;
+}
+
+bool IsValidLinkerOutputsSubstitution(const Substitution* type) {
+  // All valid compiler outputs plus the output extension.
+  return IsValidCompilerOutputsSubstitution(type) ||
+         type == &SubstitutionOutputDir || type == &SubstitutionOutputExtension;
+}
+
+bool IsValidALinkSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) ||
+         type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
+         type == &CSubstitutionLinkerInputs ||
+         type == &CSubstitutionLinkerInputsNewline ||
+         type == &CSubstitutionArFlags;
+}
diff --git a/src/gn/c_substitution_type.h b/src/gn/c_substitution_type.h
new file mode 100644 (file)
index 0000000..1f4e90e
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_C_SUBSTITUTION_TYPE_H_
+#define TOOLS_GN_C_SUBSTITUTION_TYPE_H_
+
+#include <set>
+#include <vector>
+
+#include "gn/substitution_type.h"
+
+// The set of substitutions available to all tools.
+extern const SubstitutionTypes CSubstitutions;
+
+// Valid for compiler tools.
+extern const Substitution CSubstitutionAsmFlags;
+extern const Substitution CSubstitutionCFlags;
+extern const Substitution CSubstitutionCFlagsC;
+extern const Substitution CSubstitutionCFlagsCc;
+extern const Substitution CSubstitutionCFlagsObjC;
+extern const Substitution CSubstitutionCFlagsObjCc;
+extern const Substitution CSubstitutionDefines;
+extern const Substitution CSubstitutionFrameworkDirs;
+extern const Substitution CSubstitutionIncludeDirs;
+extern const Substitution CSubstitutionModuleDeps;
+extern const Substitution CSubstitutionModuleDepsNoSelf;
+
+// Valid for linker tools.
+extern const Substitution CSubstitutionLinkerInputs;
+extern const Substitution CSubstitutionLinkerInputsNewline;
+extern const Substitution CSubstitutionLdFlags;
+extern const Substitution CSubstitutionLibs;
+extern const Substitution CSubstitutionSoLibs;
+extern const Substitution CSubstitutionFrameworks;
+extern const Substitution CSubstitutionRlibs;
+extern const Substitution CSubstitutionSwiftModules;
+
+// Valid for alink only.
+extern const Substitution CSubstitutionArFlags;
+
+// Valid for swift only.
+extern const Substitution CSubstitutionSwiftModuleName;
+extern const Substitution CSubstitutionSwiftBridgeHeader;
+extern const Substitution CSubstitutionSwiftModuleDirs;
+extern const Substitution CSubstitutionSwiftFlags;
+
+// Both compiler and linker tools.
+bool IsValidCompilerSubstitution(const Substitution* type);
+bool IsValidCompilerOutputsSubstitution(const Substitution* type);
+bool IsValidSwiftCompilerSubstitution(const Substitution* type);
+bool IsValidSwiftCompilerOutputsSubstitution(const Substitution* type);
+bool IsValidLinkerSubstitution(const Substitution* type);
+bool IsValidLinkerOutputsSubstitution(const Substitution* type);
+bool IsValidALinkSubstitution(const Substitution* type);
+
+#endif  // TOOLS_GN_C_SUBSTITUTION_TYPE_H_
diff --git a/src/gn/c_tool.cc b/src/gn/c_tool.cc
new file mode 100644 (file)
index 0000000..767b324
--- /dev/null
@@ -0,0 +1,266 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/c_tool.h"
+
+#include "base/strings/stringprintf.h"
+#include "gn/c_substitution_type.h"
+#include "gn/target.h"
+
+const char* CTool::kCToolCc = "cc";
+const char* CTool::kCToolCxx = "cxx";
+const char* CTool::kCToolCxxModule = "cxx_module";
+const char* CTool::kCToolObjC = "objc";
+const char* CTool::kCToolObjCxx = "objcxx";
+const char* CTool::kCToolRc = "rc";
+const char* CTool::kCToolAsm = "asm";
+const char* CTool::kCToolSwift = "swift";
+const char* CTool::kCToolAlink = "alink";
+const char* CTool::kCToolSolink = "solink";
+const char* CTool::kCToolSolinkModule = "solink_module";
+const char* CTool::kCToolLink = "link";
+
+CTool::CTool(const char* n)
+    : Tool(n), depsformat_(DEPS_GCC), precompiled_header_type_(PCH_NONE) {
+  CHECK(ValidateName(n));
+  set_framework_switch("-framework ");
+  set_weak_framework_switch("-weak_framework ");
+  set_framework_dir_switch("-F");
+  set_lib_dir_switch("-L");
+  set_lib_switch("-l");
+  set_linker_arg("");
+}
+
+CTool::~CTool() = default;
+
+CTool* CTool::AsC() {
+  return this;
+}
+const CTool* CTool::AsC() const {
+  return this;
+}
+
+bool CTool::ValidateName(const char* name) const {
+  return name == kCToolCc || name == kCToolCxx || name == kCToolCxxModule ||
+         name == kCToolObjC || name == kCToolObjCxx || name == kCToolRc ||
+         name == kCToolSwift || name == kCToolAsm || name == kCToolAlink ||
+         name == kCToolSolink || name == kCToolSolinkModule ||
+         name == kCToolLink;
+}
+
+void CTool::SetComplete() {
+  SetToolComplete();
+  link_output_.FillRequiredTypes(&substitution_bits_);
+  depend_output_.FillRequiredTypes(&substitution_bits_);
+}
+
+bool CTool::ValidateRuntimeOutputs(Err* err) {
+  if (runtime_outputs().list().empty())
+    return true;  // Empty is always OK.
+
+  if (name_ != kCToolSolink && name_ != kCToolSolinkModule &&
+      name_ != kCToolLink) {
+    *err = Err(defined_from(), "This tool specifies runtime_outputs.",
+               "This is only valid for linker tools (alink doesn't count).");
+    return false;
+  }
+
+  for (const SubstitutionPattern& pattern : runtime_outputs().list()) {
+    if (!IsPatternInOutputList(outputs(), pattern)) {
+      *err = Err(defined_from(), "This tool's runtime_outputs is bad.",
+                 "It must be a subset of the outputs. The bad one is:\n  " +
+                     pattern.AsString());
+      return false;
+    }
+  }
+  return true;
+}
+
+// Validates either link_output or depend_output. To generalize to either, pass
+// the associated pattern, and the variable name that should appear in error
+// messages.
+bool CTool::ValidateLinkAndDependOutput(const SubstitutionPattern& pattern,
+                                        const char* variable_name,
+                                        Err* err) {
+  if (pattern.empty())
+    return true;  // Empty is always OK.
+
+  // It should only be specified for certain tool types.
+  if (name_ != kCToolSolink && name_ != kCToolSolinkModule) {
+    *err = Err(defined_from(),
+               "This tool specifies a " + std::string(variable_name) + ".",
+               "This is only valid for solink and solink_module tools.");
+    return false;
+  }
+
+  if (!IsPatternInOutputList(outputs(), pattern)) {
+    *err = Err(defined_from(), "This tool's link_output is bad.",
+               "It must match one of the outputs.");
+    return false;
+  }
+
+  return true;
+}
+
+bool CTool::ReadPrecompiledHeaderType(Scope* scope, Err* err) {
+  const Value* value = scope->GetValue("precompiled_header_type", true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::STRING, err))
+    return false;
+
+  if (value->string_value().empty())
+    return true;  // Accept empty string, do nothing (default is "no PCH").
+
+  if (value->string_value() == "gcc") {
+    set_precompiled_header_type(PCH_GCC);
+    return true;
+  } else if (value->string_value() == "msvc") {
+    set_precompiled_header_type(PCH_MSVC);
+    return true;
+  }
+  *err = Err(*value, "Invalid precompiled_header_type",
+             "Must either be empty, \"gcc\", or \"msvc\".");
+  return false;
+}
+
+bool CTool::ReadDepsFormat(Scope* scope, Err* err) {
+  const Value* value = scope->GetValue("depsformat", true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::STRING, err))
+    return false;
+
+  if (value->string_value() == "gcc") {
+    set_depsformat(DEPS_GCC);
+  } else if (value->string_value() == "msvc") {
+    set_depsformat(DEPS_MSVC);
+  } else {
+    *err = Err(*value, "Deps format must be \"gcc\" or \"msvc\".");
+    return false;
+  }
+  return true;
+}
+
+bool CTool::ReadOutputsPatternList(Scope* scope,
+                                   const char* var,
+                                   bool required,
+                                   SubstitutionList* field,
+                                   Err* err) {
+  DCHECK(!complete_);
+  const Value* value = scope->GetValue(var, true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::LIST, err))
+    return false;
+
+  SubstitutionList list;
+  if (!list.Parse(*value, err))
+    return false;
+
+  // Validate the right kinds of patterns are used.
+  if (list.list().empty() && required) {
+    *err =
+        Err(defined_from(),
+            base::StringPrintf("\"%s\" must be specified for this tool.", var));
+    return false;
+  }
+
+  for (const auto& cur_type : list.required_types()) {
+    if (!ValidateOutputSubstitution(cur_type)) {
+      *err = Err(*value, "Pattern not valid here.",
+                 "You used the pattern " + std::string(cur_type->name) +
+                     " which is not valid\nfor this variable.");
+      return false;
+    }
+  }
+
+  *field = std::move(list);
+  return true;
+}
+
+bool CTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+  // Initialize default vars.
+  if (!Tool::InitTool(scope, toolchain, err)) {
+    return false;
+  }
+
+  // All C tools should have outputs.
+  if (!ReadOutputsPatternList(scope, "outputs", /*required=*/true, &outputs_,
+                              err)) {
+    return false;
+  }
+
+  if (!ReadDepsFormat(scope, err) || !ReadPrecompiledHeaderType(scope, err) ||
+      !ReadString(scope, "framework_switch", &framework_switch_, err) ||
+      !ReadString(scope, "weak_framework_switch", &weak_framework_switch_,
+                  err) ||
+      !ReadString(scope, "framework_dir_switch", &framework_dir_switch_, err) ||
+      !ReadString(scope, "lib_switch", &lib_switch_, err) ||
+      !ReadString(scope, "lib_dir_switch", &lib_dir_switch_, err) ||
+      !ReadPattern(scope, "link_output", &link_output_, err) ||
+      !ReadString(scope, "swiftmodule_switch", &swiftmodule_switch_, err) ||
+      !ReadPattern(scope, "depend_output", &depend_output_, err)) {
+    return false;
+  }
+
+  // Swift tool can optionally specify partial_outputs.
+  if (name_ == kCToolSwift) {
+    if (!ReadOutputsPatternList(scope, "partial_outputs", /*required=*/false,
+                                &partial_outputs_, err)) {
+      return false;
+    }
+  }
+
+  // Validate link_output and depend_output.
+  if (!ValidateLinkAndDependOutput(link_output(), "link_output", err)) {
+    return false;
+  }
+  if (!ValidateLinkAndDependOutput(depend_output(), "depend_output", err)) {
+    return false;
+  }
+  if ((!link_output().empty() && depend_output().empty()) ||
+      (link_output().empty() && !depend_output().empty())) {
+    *err = Err(defined_from(),
+               "Both link_output and depend_output should either "
+               "be specified or they should both be empty.");
+    return false;
+  }
+
+  if (!ValidateRuntimeOutputs(err)) {
+    return false;
+  }
+  return true;
+}
+
+bool CTool::ValidateSubstitution(const Substitution* sub_type) const {
+  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolCxxModule ||
+      name_ == kCToolObjC || name_ == kCToolObjCxx || name_ == kCToolRc ||
+      name_ == kCToolAsm)
+    return IsValidCompilerSubstitution(sub_type);
+  if (name_ == kCToolSwift)
+    return IsValidSwiftCompilerSubstitution(sub_type);
+  else if (name_ == kCToolAlink)
+    return IsValidALinkSubstitution(sub_type);
+  else if (name_ == kCToolSolink || name_ == kCToolSolinkModule ||
+           name_ == kCToolLink)
+    return IsValidLinkerSubstitution(sub_type);
+  NOTREACHED();
+  return false;
+}
+
+bool CTool::ValidateOutputSubstitution(const Substitution* sub_type) const {
+  if (name_ == kCToolCc || name_ == kCToolCxx || name_ == kCToolCxxModule ||
+      name_ == kCToolObjC || name_ == kCToolObjCxx || name_ == kCToolRc ||
+      name_ == kCToolAsm)
+    return IsValidCompilerOutputsSubstitution(sub_type);
+  if (name_ == kCToolSwift)
+    return IsValidSwiftCompilerOutputsSubstitution(sub_type);
+  // ALink uses the standard output file patterns as other linker tools.
+  else if (name_ == kCToolAlink || name_ == kCToolSolink ||
+           name_ == kCToolSolinkModule || name_ == kCToolLink)
+    return IsValidLinkerOutputsSubstitution(sub_type);
+  NOTREACHED();
+  return false;
+}
diff --git a/src/gn/c_tool.h b/src/gn/c_tool.h
new file mode 100644 (file)
index 0000000..fe32e53
--- /dev/null
@@ -0,0 +1,126 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_C_TOOL_H_
+#define TOOLS_GN_C_TOOL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/scope.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/tool.h"
+#include "gn/toolchain.h"
+
+class CTool : public Tool {
+ public:
+  // C compiler tools
+  static const char* kCToolCc;
+  static const char* kCToolCxx;
+  static const char* kCToolCxxModule;
+  static const char* kCToolObjC;
+  static const char* kCToolObjCxx;
+  static const char* kCToolRc;
+  static const char* kCToolAsm;
+  static const char* kCToolSwift;
+
+  // C linker tools
+  static const char* kCToolAlink;
+  static const char* kCToolSolink;
+  static const char* kCToolSolinkModule;
+  static const char* kCToolLink;
+
+  enum DepsFormat { DEPS_GCC = 0, DEPS_MSVC = 1 };
+
+  enum PrecompiledHeaderType { PCH_NONE = 0, PCH_GCC = 1, PCH_MSVC = 2 };
+
+  CTool(const char* n);
+  ~CTool();
+
+  // Manual RTTI and required functions ---------------------------------------
+
+  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+  bool ValidateName(const char* name) const override;
+  void SetComplete() override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
+
+  CTool* AsC() override;
+  const CTool* AsC() const override;
+
+  // Getters/setters ----------------------------------------------------------
+  //
+  // After the tool has had its attributes set, the caller must call
+  // SetComplete(), at which point no other changes can be made.
+
+  DepsFormat depsformat() const { return depsformat_; }
+  void set_depsformat(DepsFormat f) {
+    DCHECK(!complete_);
+    depsformat_ = f;
+  }
+
+  PrecompiledHeaderType precompiled_header_type() const {
+    return precompiled_header_type_;
+  }
+  void set_precompiled_header_type(PrecompiledHeaderType pch_type) {
+    DCHECK(!complete_);
+    precompiled_header_type_ = pch_type;
+  }
+
+  // Should match files in the outputs() if nonempty.
+  const SubstitutionPattern& link_output() const { return link_output_; }
+  void set_link_output(SubstitutionPattern link_out) {
+    DCHECK(!complete_);
+    link_output_ = std::move(link_out);
+  }
+
+  const SubstitutionPattern& depend_output() const { return depend_output_; }
+  void set_depend_output(SubstitutionPattern dep_out) {
+    DCHECK(!complete_);
+    depend_output_ = std::move(dep_out);
+  }
+
+  // Other functions ----------------------------------------------------------
+
+  // Returns true if this tool has separate outputs for dependency tracking
+  // and linking.
+  bool has_separate_solink_files() const {
+    return !link_output_.empty() || !depend_output_.empty();
+  }
+
+ private:
+  // Initialization functions -------------------------------------------------
+  //
+  // Initialization methods used by InitTool(). If successful, will set the
+  // field and return true, otherwise will return false. Must be called before
+  // SetComplete().
+  bool ValidateOutputSubstitution(const Substitution* sub_type) const;
+  bool ValidateRuntimeOutputs(Err* err);
+  // Validates either link_output or depend_output. To generalize to either,
+  // pass
+  // the associated pattern, and the variable name that should appear in error
+  // messages.
+  bool ValidateLinkAndDependOutput(const SubstitutionPattern& pattern,
+                                   const char* variable_name,
+                                   Err* err);
+  bool ReadOutputsPatternList(Scope* scope,
+                              const char* var,
+                              bool required,
+                              SubstitutionList* field,
+                              Err* err);
+  bool ReadPrecompiledHeaderType(Scope* scope, Err* err);
+  bool ReadDepsFormat(Scope* scope, Err* err);
+
+  DepsFormat depsformat_;
+  PrecompiledHeaderType precompiled_header_type_;
+  SubstitutionPattern link_output_;
+  SubstitutionPattern depend_output_;
+
+  DISALLOW_COPY_AND_ASSIGN(CTool);
+};
+
+#endif  // TOOLS_GN_C_TOOL_H_
diff --git a/src/gn/command_analyze.cc b/src/gn/command_analyze.cc
new file mode 100644 (file)
index 0000000..5c6c193
--- /dev/null
@@ -0,0 +1,148 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <iterator>
+#include <set>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "gn/analyzer.h"
+#include "gn/commands.h"
+#include "gn/filesystem_utils.h"
+#include "gn/location.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/string_utils.h"
+
+namespace commands {
+
+const char kAnalyze[] = "analyze";
+const char kAnalyze_HelpShort[] =
+    "analyze: Analyze which targets are affected by a list of files.";
+const char kAnalyze_Help[] =
+    R"*(gn analyze <out_dir> <input_path> <output_path>
+
+  Analyze which targets are affected by a list of files.
+
+  This command takes three arguments:
+
+  out_dir is the path to the build directory.
+
+  input_path is a path to a file containing a JSON object with three fields:
+
+   - "files": A list of the filenames to check.
+
+   - "test_targets": A list of the labels for targets that are needed to run
+     the tests we wish to run.
+
+   - "additional_compile_targets": A list of the labels for targets that we
+     wish to rebuild, but aren't necessarily needed for testing. The important
+     difference between this field and "test_targets" is that if an item in
+     the additional_compile_targets list refers to a group, then any
+     dependencies of that group will be returned if they are out of date, but
+     the group itself does not need to be. If the dependencies themselves are
+     groups, the same filtering is repeated. This filtering can be used to
+     avoid rebuilding dependencies of a group that are unaffected by the input
+     files. The list may also contain the string "all" to refer to a
+     pseudo-group that contains every root target in the build graph.
+
+     This filtering behavior is also known as "pruning" the list of compile
+     targets.
+
+  If input_path is -, input is read from stdin.
+
+  output_path is a path indicating where the results of the command are to be
+  written. The results will be a file containing a JSON object with one or more
+  of following fields:
+
+   - "compile_targets": A list of the labels derived from the input
+     compile_targets list that are affected by the input files. Due to the way
+     the filtering works for compile targets as described above, this list may
+     contain targets that do not appear in the input list.
+
+   - "test_targets": A list of the labels from the input test_targets list that
+     are affected by the input files. This list will be a proper subset of the
+     input list.
+
+   - "invalid_targets": A list of any names from the input that do not exist in
+     the build graph. If this list is non-empty, the "error" field will also be
+     set to "Invalid targets".
+
+   - "status": A string containing one of three values:
+
+       - "Found dependency"
+       - "No dependency"
+       - "Found dependency (all)"
+
+     In the first case, the lists returned in compile_targets and test_targets
+     should be passed to ninja to build. In the second case, nothing was
+     affected and no build is necessary. In the third case, GN could not
+     determine the correct answer and returned the input as the output in order
+     to be safe.
+
+   - "error": This will only be present if an error occurred, and will contain
+     a string describing the error. This includes cases where the input file is
+     not in the right format, or contains invalid targets.
+
+  If output_path is -, output is written to stdout.
+
+  The command returns 1 if it is unable to read the input file or write the
+  output file, or if there is something wrong with the build such that gen
+  would also fail, and 0 otherwise. In particular, it returns 0 even if the
+  "error" key is non-empty and a non-fatal error occurred. In other words, it
+  tries really hard to always write something to the output JSON and convey
+  errors that way rather than via return codes.
+)*";
+
+int RunAnalyze(const std::vector<std::string>& args) {
+  if (args.size() != 3) {
+    Err(Location(), "Unknown command format. See \"gn help analyze\"",
+        "Usage: \"gn analyze <out_dir> <input_path> <output_path>")
+        .PrintToStdout();
+    return 1;
+  }
+
+  std::string input;
+  if (args[1] == "-") {
+    input = ReadStdin();
+  } else {
+    bool ret = base::ReadFileToString(UTF8ToFilePath(args[1]), &input);
+    if (!ret) {
+      Err(Location(), "Input file " + args[1] + " not found.").PrintToStdout();
+      return 1;
+    }
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false) || !setup->Run())
+    return 1;
+
+  Err err;
+  Analyzer analyzer(
+      setup->builder(), setup->build_settings().build_config_file(),
+      setup->GetDotFile(),
+      setup->build_settings().build_args().build_args_dependency_files());
+  std::string output = analyzer.Analyze(input, &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (args[2] == "-") {
+    OutputString(output + "\n");
+  } else {
+    WriteFile(UTF8ToFilePath(args[2]), output, &err);
+    if (err.has_error()) {
+      err.PrintToStdout();
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_args.cc b/src/gn/command_args.cc
new file mode 100644 (file)
index 0000000..14ca348
--- /dev/null
@@ -0,0 +1,511 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <map>
+
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "gn/commands.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/tokenizer.h"
+#include "gn/trace.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include <shellapi.h>
+#endif
+
+namespace commands {
+
+namespace {
+
+const char kSwitchList[] = "list";
+const char kSwitchShort[] = "short";
+const char kSwitchOverridesOnly[] = "overrides-only";
+const char kSwitchJson[] = "json";
+
+bool DoesLineBeginWithComment(const std::string_view& line) {
+  // Skip whitespace.
+  size_t i = 0;
+  while (i < line.size() && base::IsAsciiWhitespace(line[i]))
+    i++;
+
+  return i < line.size() && line[i] == '#';
+}
+
+// Returns the offset of the beginning of the line identified by |offset|.
+size_t BackUpToLineBegin(const std::string& data, size_t offset) {
+  // Degenerate case of an empty line. Below we'll try to return the
+  // character after the newline, but that will be incorrect in this case.
+  if (offset == 0 || Tokenizer::IsNewline(data, offset))
+    return offset;
+
+  size_t cur = offset;
+  do {
+    cur--;
+    if (Tokenizer::IsNewline(data, cur))
+      return cur + 1;  // Want the first character *after* the newline.
+  } while (cur > 0);
+  return 0;
+}
+
+// Assumes DoesLineBeginWithComment(), this strips the # character from the
+// beginning and normalizes preceding whitespace.
+std::string StripHashFromLine(const std::string_view& line, bool pad) {
+  // Replace the # sign and everything before it with 3 spaces, so that a
+  // normal comment that has a space after the # will be indented 4 spaces
+  // (which makes our formatting come out nicely). If the comment is indented
+  // from there, we want to preserve that indenting.
+  std::string line_stripped(line.substr(line.find('#') + 1));
+  if (pad)
+    return "   " + line_stripped;
+
+  // If not padding, strip the leading space if present.
+  if (!line_stripped.empty() && line_stripped[0] == ' ')
+    return line_stripped.substr(1);
+  return line_stripped;
+}
+
+// Tries to find the comment before the setting of the given value.
+void GetContextForValue(const Value& value,
+                        std::string* location_str,
+                        int* line_no,
+                        std::string* comment,
+                        bool pad_comment = true) {
+  Location location = value.origin()->GetRange().begin();
+  const InputFile* file = location.file();
+  if (!file)
+    return;
+
+  *location_str = file->name().value();
+  *line_no = location.line_number();
+
+  const std::string& data = file->contents();
+  size_t line_off =
+      Tokenizer::ByteOffsetOfNthLine(data, location.line_number());
+
+  while (line_off > 1) {
+    line_off -= 2;  // Back up to end of previous line.
+    size_t previous_line_offset = BackUpToLineBegin(data, line_off);
+
+    std::string_view line(&data[previous_line_offset],
+                          line_off - previous_line_offset + 1);
+    if (!DoesLineBeginWithComment(line))
+      break;
+
+    comment->insert(0, StripHashFromLine(line, pad_comment) + "\n");
+    line_off = previous_line_offset;
+  }
+}
+
+// Prints the value and origin for a default value. Default values always list
+// an origin and if there is no origin, print a message about it being
+// internally set. Overrides can't be internally set so the location handling
+// is a bit different.
+//
+// The default value also contains the docstring.
+void PrintDefaultValueInfo(std::string_view name, const Value& value) {
+  OutputString(value.ToString(true) + "\n");
+  if (value.origin()) {
+    int line_no;
+    std::string location, comment;
+    GetContextForValue(value, &location, &line_no, &comment);
+    OutputString("      From " + location + ":" + base::IntToString(line_no) +
+                 "\n");
+    if (!comment.empty())
+      OutputString("\n" + comment);
+  } else {
+    OutputString("      (Internally set; try `gn help " + std::string(name) +
+                 "`.)\n");
+  }
+}
+
+// Override value is null if there is no override.
+void PrintArgHelp(const std::string_view& name,
+                  const Args::ValueWithOverride& val) {
+  OutputString(std::string(name), DECORATION_YELLOW);
+  OutputString("\n");
+
+  if (val.has_override) {
+    // Override present, print both it and the default.
+    OutputString("    Current value = " + val.override_value.ToString(true) +
+                 "\n");
+    if (val.override_value.origin()) {
+      int line_no;
+      std::string location, comment;
+      GetContextForValue(val.override_value, &location, &line_no, &comment);
+      OutputString("      From " + location + ":" + base::IntToString(line_no) +
+                   "\n");
+    }
+    OutputString("    Overridden from the default = ");
+    PrintDefaultValueInfo(name, val.default_value);
+  } else {
+    // No override.
+    OutputString("    Current value (from the default) = ");
+    PrintDefaultValueInfo(name, val.default_value);
+  }
+}
+
+void BuildArgJson(base::Value& dict,
+                  const std::string_view& name,
+                  const Args::ValueWithOverride& arg,
+                  bool short_only) {
+  assert(dict.is_dict());
+
+  // Fetch argument name.
+  dict.SetKey("name", base::Value(name));
+
+  // Fetch overridden value information (if present).
+  if (arg.has_override) {
+    base::DictionaryValue override_dict;
+    override_dict.SetKey("value",
+                         base::Value(arg.override_value.ToString(true)));
+    if (arg.override_value.origin() && !short_only) {
+      int line_no;
+      std::string location, comment;
+      GetContextForValue(arg.override_value, &location, &line_no, &comment,
+                         /*pad_comment=*/false);
+      override_dict.SetKey("file", base::Value(location));
+      override_dict.SetKey("line", base::Value(line_no));
+    }
+    dict.SetKey("current", std::move(override_dict));
+  }
+
+  // Fetch default value information, and comment (if present).
+  base::DictionaryValue default_dict;
+  std::string comment;
+  default_dict.SetKey("value", base::Value(arg.default_value.ToString(true)));
+  if (arg.default_value.origin() && !short_only) {
+    int line_no;
+    std::string location;
+    GetContextForValue(arg.default_value, &location, &line_no, &comment,
+                       /*pad_comment=*/false);
+    default_dict.SetKey("file", base::Value(location));
+    default_dict.SetKey("line", base::Value(line_no));
+  }
+  dict.SetKey("default", std::move(default_dict));
+  if (!comment.empty() && !short_only)
+    dict.SetKey("comment", base::Value(comment));
+}
+
+int ListArgs(const std::string& build_dir) {
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(build_dir, false) || !setup->Run())
+    return 1;
+
+  Args::ValueWithOverrideMap args =
+      setup->build_settings().build_args().GetAllArguments();
+  std::string list_value =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
+  if (!list_value.empty()) {
+    // List just the one specified as the parameter to --list.
+    auto found = args.find(list_value);
+    if (found == args.end()) {
+      Err(Location(), "Unknown build argument.",
+          "You asked for \"" + list_value +
+              "\" which I didn't find in any "
+              "build file\nassociated with this build.")
+          .PrintToStdout();
+      return 1;
+    }
+
+    // Delete everything from the map except the one requested.
+    Args::ValueWithOverrideMap::value_type preserved = *found;
+    args.clear();
+    args.insert(preserved);
+  }
+
+  // Cache this to avoid looking it up for each |arg| in the loops below.
+  const bool overrides_only =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchOverridesOnly);
+  const bool short_only =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort);
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchJson)) {
+    // Convert all args to JSON, serialize and print them
+    auto list = std::make_unique<base::ListValue>();
+    for (const auto& arg : args) {
+      if (overrides_only && !arg.second.has_override)
+        continue;
+      list->GetList().emplace_back(base::DictionaryValue());
+      BuildArgJson(list->GetList().back(), arg.first, arg.second, short_only);
+    }
+    std::string s;
+    base::JSONWriter::WriteWithOptions(
+        *list.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
+    OutputString(s);
+    return 0;
+  }
+
+  if (short_only) {
+    // Short <key>=<current_value> output.
+    for (const auto& arg : args) {
+      if (overrides_only && !arg.second.has_override)
+        continue;
+      OutputString(std::string(arg.first));
+      OutputString(" = ");
+      if (arg.second.has_override)
+        OutputString(arg.second.override_value.ToString(true));
+      else
+        OutputString(arg.second.default_value.ToString(true));
+      OutputString("\n");
+    }
+    return 0;
+  }
+
+  // Long output.
+  for (const auto& arg : args) {
+    if (overrides_only && !arg.second.has_override)
+      continue;
+    PrintArgHelp(arg.first, arg.second);
+    OutputString("\n");
+  }
+
+  return 0;
+}
+
+#if defined(OS_WIN)
+
+bool RunEditor(const base::FilePath& file_to_edit) {
+  SHELLEXECUTEINFO info;
+  memset(&info, 0, sizeof(info));
+  info.cbSize = sizeof(info);
+  info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME;
+  info.lpFile = reinterpret_cast<LPCWSTR>(file_to_edit.value().c_str());
+  info.nShow = SW_SHOW;
+  info.lpClass = L".txt";
+  if (!::ShellExecuteEx(&info)) {
+    Err(Location(), "Couldn't run editor.",
+        "Just edit \"" + FilePathToUTF8(file_to_edit) + "\" manually instead.")
+        .PrintToStdout();
+    return false;
+  }
+
+  if (!info.hProcess) {
+    // Windows re-used an existing process.
+    OutputString("\"" + FilePathToUTF8(file_to_edit) +
+                 "\" opened in editor, save it and press <Enter> when done.\n");
+    getchar();
+  } else {
+    OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) +
+                 "\"...\n");
+    ::WaitForSingleObject(info.hProcess, INFINITE);
+    ::CloseHandle(info.hProcess);
+  }
+  return true;
+}
+
+#else  // POSIX
+
+bool RunEditor(const base::FilePath& file_to_edit) {
+  const char* editor_ptr = getenv("GN_EDITOR");
+  if (!editor_ptr)
+    editor_ptr = getenv("VISUAL");
+  if (!editor_ptr)
+    editor_ptr = getenv("EDITOR");
+  if (!editor_ptr)
+    editor_ptr = "vi";
+
+  std::string cmd(editor_ptr);
+  cmd.append(" \"");
+
+  // Its impossible to do this properly since we don't know the user's shell,
+  // but quoting and escaping internal quotes should handle 99.999% of all
+  // cases.
+  std::string escaped_name = file_to_edit.value();
+  base::ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\"");
+  cmd.append(escaped_name);
+  cmd.push_back('"');
+
+  OutputString("Waiting for editor on \"" + file_to_edit.value() + "\"...\n");
+  return system(cmd.c_str()) == 0;
+}
+
+#endif
+
+int EditArgsFile(const std::string& build_dir) {
+  {
+    // Scope the setup. We only use it for some basic state. We'll do the
+    // "real" build below in the gen command.
+    Setup setup;
+    // Don't fill build arguments. We're about to edit the file which supplies
+    // these in the first place.
+    setup.set_fill_arguments(false);
+    if (!setup.DoSetup(build_dir, true))
+      return 1;
+
+    // Ensure the file exists. Need to normalize path separators since on
+    // Windows they can come out as forward slashes here, and that confuses some
+    // of the commands.
+    BuildSettings build_settings = setup.build_settings();
+    base::FilePath arg_file =
+        build_settings.GetFullPath(setup.GetBuildArgFile())
+            .NormalizePathSeparators();
+    if (!base::PathExists(arg_file)) {
+      std::string argfile_default_contents =
+          "# Build arguments go here.\n"
+          "# See \"gn args <out_dir> --list\" for available build "
+          "arguments.\n";
+
+      SourceFile template_path = build_settings.arg_file_template_path();
+      if (!template_path.is_null()) {
+        base::FilePath full_path =
+            build_settings.GetFullPath(template_path).NormalizePathSeparators();
+        if (!base::PathExists(full_path)) {
+          Err err =
+              Err(Location(), std::string("Can't load arg_file_template:\n  ") +
+                                  template_path.value());
+          err.PrintToStdout();
+          return 1;
+        }
+
+        // Ignore the return code; if the read fails (unlikely), we'll just
+        // use the default contents.
+        base::ReadFileToString(full_path, &argfile_default_contents);
+      }
+#if defined(OS_WIN)
+      // Use Windows lineendings for this file since it will often open in
+      // Notepad which can't handle Unix ones.
+      base::ReplaceSubstringsAfterOffset(&argfile_default_contents, 0, "\n",
+                                         "\r\n");
+#endif
+      base::CreateDirectory(arg_file.DirName());
+      base::WriteFile(arg_file, argfile_default_contents.c_str(),
+                      static_cast<int>(argfile_default_contents.size()));
+    }
+
+    ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor");
+    if (!RunEditor(arg_file))
+      return 1;
+  }
+
+  // Now do a normal "gen" command.
+  OutputString("Generating files...\n");
+  std::vector<std::string> gen_commands;
+  gen_commands.push_back(build_dir);
+  return RunGen(gen_commands);
+}
+
+}  // namespace
+
+const char kArgs[] = "args";
+const char kArgs_HelpShort[] =
+    "args: Display or configure arguments declared by the build.";
+const char kArgs_Help[] =
+    R"(gn args: (command-line tool)
+
+  Display or configure arguments declared by the build.
+
+    gn args <out_dir> [--list] [--short] [--args] [--overrides-only]
+
+  See also "gn help buildargs" for a more high-level overview of how
+  build arguments work.
+
+Usage
+
+  gn args <out_dir>
+      Open the arguments for the given build directory in an editor. If the
+      given build directory doesn't exist, it will be created and an empty args
+      file will be opened in the editor. You would type something like this
+      into that file:
+          enable_doom_melon=false
+          os="android"
+
+      To find your editor on Posix, GN will search the environment variables in
+      order: GN_EDITOR, VISUAL, and EDITOR. On Windows GN will open the command
+      associated with .txt files.
+
+      Note: you can edit the build args manually by editing the file "args.gn"
+      in the build directory and then running "gn gen <out_dir>".
+
+  gn args <out_dir> --list[=<exact_arg>] [--short] [--overrides-only] [--json]
+      Lists all build arguments available in the current configuration, or, if
+      an exact_arg is specified for the list flag, just that one build
+      argument.
+
+      The output will list the declaration location, current value for the
+      build, default value (if different than the current value), and comment
+      preceding the declaration.
+
+      If --short is specified, only the names and current values will be
+      printed.
+
+      If --overrides-only is specified, only the names and current values of
+      arguments that have been overridden (i.e. non-default arguments) will
+      be printed. Overrides come from the <out_dir>/args.gn file and //.gn
+
+      If --json is specified, the output will be emitted in json format.
+      JSON schema for output:
+      [
+        {
+          "name": variable_name,
+          "current": {
+            "value": overridden_value,
+            "file": file_name,
+            "line": line_no
+          },
+          "default": {
+            "value": default_value,
+            "file": file_name,
+            "line": line_no
+          },
+          "comment": comment_string
+        },
+        ...
+      ]
+
+Examples
+
+  gn args out/Debug
+    Opens an editor with the args for out/Debug.
+
+  gn args out/Debug --list --short
+    Prints all arguments with their default values for the out/Debug
+    build.
+
+  gn args out/Debug --list --short --overrides-only
+    Prints overridden arguments for the out/Debug build.
+
+  gn args out/Debug --list=target_cpu
+    Prints information about the "target_cpu" argument for the "
+   "out/Debug
+    build.
+
+  gn args --list --args="os=\"android\" enable_doom_melon=true"
+    Prints all arguments with the default values for a build with the
+    given arguments set (which may affect the values of other
+    arguments).
+)";
+
+int RunArgs(const std::vector<std::string>& args) {
+  if (args.size() != 1) {
+    Err(Location(), "Exactly one build dir needed.",
+        "Usage: \"gn args <out_dir>\"\n"
+        "Or see \"gn help args\" for more variants.")
+        .PrintToStdout();
+    return 1;
+  }
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList))
+    return ListArgs(args[0]);
+  return EditArgsFile(args[0]);
+}
+
+}  // namespace commands
diff --git a/src/gn/command_check.cc b/src/gn/command_check.cc
new file mode 100644 (file)
index 0000000..536bc13
--- /dev/null
@@ -0,0 +1,285 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "gn/commands.h"
+#include "gn/header_checker.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+
+namespace commands {
+
+const char kNoGnCheck_Help[] =
+    R"(nogncheck: Skip an include line from checking.
+
+  GN's header checker helps validate that the includes match the build
+  dependency graph. Sometimes an include might be conditional or otherwise
+  problematic, but you want to specifically allow it. In this case, it can be
+  whitelisted.
+
+  Include lines containing the substring "nogncheck" will be excluded from
+  header checking. The most common case is a conditional include:
+
+    #if defined(ENABLE_DOOM_MELON)
+    #include "tools/doom_melon/doom_melon.h"  // nogncheck
+    #endif
+
+  If the build file has a conditional dependency on the corresponding target
+  that matches the conditional include, everything will always link correctly:
+
+    source_set("mytarget") {
+      ...
+      if (enable_doom_melon) {
+        defines = [ "ENABLE_DOOM_MELON" ]
+        deps += [ "//tools/doom_melon" ]
+      }
+
+  But GN's header checker does not understand preprocessor directives, won't
+  know it matches the build dependencies, and will flag this include as
+  incorrect when the condition is false.
+
+More information
+
+  The topic "gn help check" has general information on how checking works and
+  advice on fixing problems. Targets can also opt-out of checking, see
+  "gn help check_includes".
+)";
+
+const char kCheck[] = "check";
+const char kCheck_HelpShort[] = "check: Check header dependencies.";
+const char kCheck_Help[] =
+    R"(gn check <out_dir> [<label_pattern>] [--force] [--check-generated]
+
+  GN's include header checker validates that the includes for C-like source
+  files match the build dependency graph.
+
+  "gn check" is the same thing as "gn gen" with the "--check" flag except that
+  this command does not write out any build files. It's intended to be an easy
+  way to manually trigger include file checking.
+
+  The <label_pattern> can take exact labels or patterns that match more than
+  one (although not general regular expressions). If specified, only those
+  matching targets will be checked. See "gn help label_pattern" for details.
+
+Command-specific switches
+
+  --check-generated
+      Generated files are normally not checked since they do not exist
+      until after a build. With this flag, those generated files that
+      can be found on disk are also checked.
+
+  --check-system
+     Check system style includes (using <angle brackets>) in addition to
+     "double quote" includes.
+
+)" DEFAULT_TOOLCHAIN_SWITCH_HELP
+    R"(
+  --force
+      Ignores specifications of "check_includes = false" and checks all
+      target's files that match the target label.
+
+What gets checked
+
+  The .gn file may specify a list of targets to be checked in the list
+  check_targets (see "gn help dotfile"). Alternatively, the .gn file may
+  specify a list of targets not to be checked in no_check_targets. If a label
+  pattern is specified on the command line, neither check_targets or
+  no_check_targets is used.
+
+  Targets can opt-out from checking with "check_includes = false" (see
+  "gn help check_includes").
+
+  For targets being checked:
+
+    - GN opens all C-like source files in the targets to be checked and scans
+      the top for includes.
+
+    - Generated files (that might not exist yet) are ignored unless
+      the --check-generated flag is provided.
+
+    - Includes with a "nogncheck" annotation are skipped (see
+      "gn help nogncheck").
+
+    - Includes using "quotes" are always checked.
+        If system style checking is enabled, includes using <angle brackets>
+        are also checked.
+
+    - Include paths are assumed to be relative to any of the "include_dirs" for
+      the target (including the implicit current dir).
+
+    - GN does not run the preprocessor so will not understand conditional
+      includes.
+
+    - Only includes matching known files in the build are checked: includes
+      matching unknown paths are ignored.
+
+  For an include to be valid:
+
+    - The included file must be in the current target, or there must be a path
+      following only public dependencies to a target with the file in it
+      ("gn path" is a good way to diagnose problems).
+
+    - There can be multiple targets with an included file: only one needs to be
+      valid for the include to be allowed.
+
+    - If there are only "sources" in a target, all are considered to be public
+      and can be included by other targets with a valid public dependency path.
+
+    - If a target lists files as "public", only those files are able to be
+      included by other targets. Anything in the sources will be considered
+      private and will not be includable regardless of dependency paths.
+
+    - Outputs from actions are treated like public sources on that target.
+
+    - A target can include headers from a target that depends on it if the
+      other target is annotated accordingly. See "gn help
+      allow_circular_includes_from".
+
+Advice on fixing problems
+
+  If you have a third party project that is difficult to fix or doesn't care
+  about include checks it's generally best to exclude that target from checking
+  altogether via "check_includes = false".
+
+  If you have conditional includes, make sure the build conditions and the
+  preprocessor conditions match, and annotate the line with "nogncheck" (see
+  "gn help nogncheck" for an example).
+
+  If two targets are hopelessly intertwined, use the
+  "allow_circular_includes_from" annotation. Ideally each should have identical
+  dependencies so configs inherited from those dependencies are consistent (see
+  "gn help allow_circular_includes_from").
+
+  If you have a standalone header file or files that need to be shared between
+  a few targets, you can consider making a source_set listing only those
+  headers as public sources. With only header files, the source set will be a
+  no-op from a build perspective, but will give a central place to refer to
+  those headers. That source set's files will still need to pass "gn check" in
+  isolation.
+
+  In rare cases it makes sense to list a header in more than one target if it
+  could be considered conceptually a member of both.
+
+Examples
+
+  gn check out/Debug
+      Check everything.
+
+  gn check out/Default //foo:bar
+      Check only the files in the //foo:bar target.
+
+  gn check out/Default "//foo/*
+      Check only the files in targets in the //foo directory tree.
+)";
+
+int RunCheck(const std::vector<std::string>& args) {
+  if (args.size() != 1 && args.size() != 2) {
+    Err(Location(), "Unknown command format. See \"gn help check\"",
+        "Usage: \"gn check <out_dir> [<target_label>]\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup();
+  if (!setup->DoSetup(args[0], false))
+    return 1;
+  if (!setup->Run())
+    return 1;
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain);
+
+  std::vector<const Target*> all_targets =
+      setup->builder().GetAllResolvedTargets();
+
+  bool filtered_by_build_config = false;
+  std::vector<const Target*> targets_to_check;
+  if (args.size() > 1) {
+    // Compute the targets to check.
+    std::vector<std::string> inputs(args.begin() + 1, args.end());
+    UniqueVector<const Target*> target_matches;
+    UniqueVector<const Config*> config_matches;
+    UniqueVector<const Toolchain*> toolchain_matches;
+    UniqueVector<SourceFile> file_matches;
+    if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only,
+                                     &target_matches, &config_matches,
+                                     &toolchain_matches, &file_matches))
+      return 1;
+
+    if (target_matches.size() == 0) {
+      OutputString("No matching targets.\n");
+      return 1;
+    }
+    targets_to_check.insert(targets_to_check.begin(), target_matches.begin(),
+                            target_matches.end());
+  } else {
+    // No argument means to check everything allowed by the filter in
+    // the build config file.
+    if (setup->check_patterns()) {
+      FilterTargetsByPatterns(all_targets, *setup->check_patterns(),
+                              &targets_to_check);
+      filtered_by_build_config = targets_to_check.size() != all_targets.size();
+    } else if (setup->no_check_patterns()) {
+      FilterOutTargetsByPatterns(all_targets, *setup->no_check_patterns(),
+                                 &targets_to_check);
+      filtered_by_build_config = targets_to_check.size() != all_targets.size();
+    } else {
+      // No global filter, check everything.
+      targets_to_check = all_targets;
+    }
+  }
+
+  bool force = cmdline->HasSwitch("force");
+  bool check_generated = cmdline->HasSwitch("check-generated");
+  bool check_system =
+      setup->check_system_includes() || cmdline->HasSwitch("check-system");
+
+  if (!CheckPublicHeaders(&setup->build_settings(), all_targets,
+                          targets_to_check, force, check_generated,
+                          check_system))
+    return 1;
+
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
+    if (filtered_by_build_config) {
+      // Tell the user about the implicit filtering since this is obscure.
+      OutputString(base::StringPrintf(
+          "%d targets out of %d checked based on the check_targets or "
+          "no_check_targets defined in \".gn\".\n",
+          static_cast<int>(targets_to_check.size()),
+          static_cast<int>(all_targets.size())));
+    }
+    OutputString("Header dependency check OK\n", DECORATION_GREEN);
+  }
+  return 0;
+}
+
+bool CheckPublicHeaders(const BuildSettings* build_settings,
+                        const std::vector<const Target*>& all_targets,
+                        const std::vector<const Target*>& to_check,
+                        bool force_check,
+                        bool check_generated,
+                        bool check_system) {
+  ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
+
+  scoped_refptr<HeaderChecker> header_checker(new HeaderChecker(
+      build_settings, all_targets, check_generated, check_system));
+
+  std::vector<Err> header_errors;
+  header_checker->Run(to_check, force_check, &header_errors);
+  for (size_t i = 0; i < header_errors.size(); i++) {
+    if (i > 0)
+      OutputString("___________________\n", DECORATION_YELLOW);
+    header_errors[i].PrintToStdout();
+  }
+  return header_errors.empty();
+}
+
+}  // namespace commands
diff --git a/src/gn/command_clean.cc b/src/gn/command_clean.cc
new file mode 100644 (file)
index 0000000..5290dd6
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "gn/commands.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/setup.h"
+
+namespace {
+
+// Extracts from a build.ninja the commands to run GN.
+//
+// The commands to run GN are the gn rule and build.ninja build step at the top
+// of the build.ninja file. We want to keep these when deleting GN builds since
+// we want to preserve the command-line flags to GN.
+//
+// On error, returns the empty string.
+std::string ExtractGNBuildCommands(const base::FilePath& build_ninja_file) {
+  std::string file_contents;
+  if (!base::ReadFileToString(build_ninja_file, &file_contents))
+    return std::string();
+
+  std::vector<std::string_view> lines = base::SplitStringPiece(
+      file_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  std::string result;
+  int num_blank_lines = 0;
+  for (const auto& line : lines) {
+    result.append(line);
+    result.push_back('\n');
+    if (line.empty())
+      ++num_blank_lines;
+    if (num_blank_lines == 3)
+      break;
+  }
+
+  return result;
+}
+
+bool CleanOneDir(const std::string& dir) {
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(dir, false))
+    return false;
+
+  base::FilePath build_dir(setup->build_settings().GetFullPath(
+      SourceDir(setup->build_settings().build_dir().value())));
+
+  // NOTE: Not all GN builds have args.gn file hence we check here
+  // if a build.ninja.d files exists instead.
+  base::FilePath build_ninja_d_file = build_dir.AppendASCII("build.ninja.d");
+  if (!base::PathExists(build_ninja_d_file)) {
+    Err(Location(),
+        base::StringPrintf(
+            "%s does not look like a build directory.\n",
+            FilePathToUTF8(build_ninja_d_file.DirName().value()).c_str()))
+        .PrintToStdout();
+    return false;
+  }
+
+  // Erase everything but the args file, and write a dummy build.ninja file that
+  // will automatically rerun GN the next time Ninja is run.
+  base::FilePath build_ninja_file = build_dir.AppendASCII("build.ninja");
+  std::string build_commands = ExtractGNBuildCommands(build_ninja_file);
+  if (build_commands.empty()) {
+    // Couldn't parse the build.ninja file.
+    Err(Location(), "Couldn't read build.ninja in this directory.",
+        "Try running \"gn gen\" on it and then re-running \"gn clean\".")
+        .PrintToStdout();
+    return false;
+  }
+
+  base::FileEnumerator traversal(
+      build_dir, false,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+  for (base::FilePath current = traversal.Next(); !current.empty();
+       current = traversal.Next()) {
+    if (base::ToLowerASCII(current.BaseName().value()) !=
+        FILE_PATH_LITERAL("args.gn")) {
+      base::DeleteFile(current, true);
+    }
+  }
+
+  // Write the build.ninja file sufficiently to regenerate itself.
+  if (base::WriteFile(build_ninja_file, build_commands.data(),
+                      static_cast<int>(build_commands.size())) == -1) {
+    Err(Location(), std::string("Failed to write build.ninja."))
+        .PrintToStdout();
+    return false;
+  }
+
+  // Write a .d file for the build which references a nonexistent file.
+  // This will make Ninja always mark the build as dirty.
+  std::string dummy_content("build.ninja: nonexistant_file.gn\n");
+  if (base::WriteFile(build_ninja_d_file, dummy_content.data(),
+                      static_cast<int>(dummy_content.size())) == -1) {
+    Err(Location(), std::string("Failed to write build.ninja.d."))
+        .PrintToStdout();
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+namespace commands {
+
+const char kClean[] = "clean";
+const char kClean_HelpShort[] = "clean: Cleans the output directory.";
+const char kClean_Help[] =
+    "gn clean <out_dir>...\n"
+    "\n"
+    "  Deletes the contents of the output directory except for args.gn and\n"
+    "  creates a Ninja build environment sufficient to regenerate the build.\n";
+
+int RunClean(const std::vector<std::string>& args) {
+  if (args.empty()) {
+    Err(Location(), "Missing argument.", "Usage: \"gn clean <out_dir>...\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  for (const auto& dir : args) {
+    if (!CleanOneDir(dir))
+      return 1;
+  }
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_clean_stale.cc b/src/gn/command_clean_stale.cc
new file mode 100644 (file)
index 0000000..72f621d
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <optional>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "gn/commands.h"
+#include "gn/err.h"
+#include "gn/ninja_tools.h"
+#include "gn/setup.h"
+#include "gn/source_dir.h"
+#include "gn/switches.h"
+#include "gn/version.h"
+
+namespace commands {
+
+namespace {
+
+bool CleanStaleOneDir(const base::FilePath& ninja_executable,
+                      const std::string& dir) {
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(dir, false))
+    return false;
+
+  base::FilePath build_dir(setup->build_settings().GetFullPath(
+      SourceDir(setup->build_settings().build_dir().value())));
+
+  // The order of operations for these tools is:
+  // 1. cleandead - This eliminates old files from the build directory.
+  // 2. recompact - This prunes old entries from the ninja log and deps files.
+  //
+  // This order is ideal because the files removed by cleandead will no longer
+  // be found during the recompact, so ninja can prune their entries.
+  Err err;
+  if (!InvokeNinjaCleanDeadTool(ninja_executable, build_dir, &err)) {
+    err.PrintToStdout();
+    return false;
+  }
+
+  if (!InvokeNinjaRecompactTool(ninja_executable, build_dir, &err)) {
+    err.PrintToStdout();
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+const char kCleanStale[] = "clean_stale";
+const char kCleanStale_HelpShort[] =
+    "clean_stale: Cleans the stale output files from the output directory.";
+const char kCleanStale_Help[] =
+    R"(gn clean_stale [--ninja-executable=...] <out_dir>...
+
+  Removes the no longer needed output files from the build directory and prunes
+  their records from the ninja build log and dependency database. These are
+  output files that were generated from previous builds, but the current build
+  graph no longer references them.
+
+  This command requires a ninja executable of at least version 1.10.0. The
+  executable must be provided by the --ninja-executable switch.
+
+Options
+
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use.
+)";
+
+int RunCleanStale(const std::vector<std::string>& args) {
+  if (args.empty()) {
+    Err(Location(), "Missing argument.",
+        "Usage: \"gn clean_stale <out_dir>...\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  base::FilePath ninja_executable = cmdline->GetSwitchValuePath(switches::kNinjaExecutable);
+  if (ninja_executable.empty()) {
+    Err(Location(), "No --ninja-executable provided.",
+        "--clean-stale requires a ninja executable to run. You can "
+        "provide one on the command line via --ninja-executable.")
+        .PrintToStdout();
+    return 1;
+  }
+
+  for (const std::string& dir : args) {
+    if (!CleanStaleOneDir(ninja_executable, dir))
+      return 1;
+  }
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
new file mode 100644 (file)
index 0000000..82a3822
--- /dev/null
@@ -0,0 +1,716 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <sstream>
+
+#include "base/command_line.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "gn/commands.h"
+#include "gn/config.h"
+#include "gn/desc_builder.h"
+#include "gn/rust_variables.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/swift_variables.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+
+namespace commands {
+
+namespace {
+
+// Desc-specific command line switches.
+const char kBlame[] = "blame";
+const char kTree[] = "tree";
+const char kAll[] = "all";
+
+void PrintDictValue(const base::Value* value,
+                    int indentLevel,
+                    bool use_first_indent) {
+  std::string indent(indentLevel * 2, ' ');
+  const base::ListValue* list_value = nullptr;
+  const base::DictionaryValue* dict_value = nullptr;
+  std::string string_value;
+  bool bool_value = false;
+  int int_value = 0;
+  if (use_first_indent)
+    OutputString(indent);
+  if (value->GetAsList(&list_value)) {
+    OutputString("[\n");
+    bool first = true;
+    for (const auto& v : *list_value) {
+      if (!first)
+        OutputString(",\n");
+      PrintDictValue(&v, indentLevel + 1, true);
+      first = false;
+    }
+    OutputString("\n" + indent + "]");
+  } else if (value->GetAsString(&string_value)) {
+    OutputString("\"" + string_value + "\"");
+  } else if (value->GetAsBoolean(&bool_value)) {
+    OutputString(bool_value ? "true" : "false");
+  } else if (value->GetAsDictionary(&dict_value)) {
+    OutputString("{\n");
+    std::string indent_plus_one((indentLevel + 1) * 2, ' ');
+    base::DictionaryValue::Iterator iter(*dict_value);
+    bool first = true;
+    while (!iter.IsAtEnd()) {
+      if (!first)
+        OutputString(",\n");
+      OutputString(indent_plus_one + iter.key() + " = ");
+      PrintDictValue(&iter.value(), indentLevel + 1, false);
+      iter.Advance();
+      first = false;
+    }
+    OutputString("\n" + indent + "}");
+  } else if (value->GetAsInteger(&int_value)) {
+    OutputString(base::IntToString(int_value));
+  } else if (value->is_none()) {
+    OutputString("<null>");
+  }
+}
+
+// Prints value with specified indentation level
+void PrintValue(const base::Value* value, int indentLevel) {
+  std::string indent(indentLevel * 2, ' ');
+  const base::ListValue* list_value = nullptr;
+  const base::DictionaryValue* dict_value = nullptr;
+  std::string string_value;
+  bool bool_value = false;
+  int int_value = 0;
+  if (value->GetAsList(&list_value)) {
+    for (const auto& v : *list_value) {
+      PrintValue(&v, indentLevel);
+    }
+  } else if (value->GetAsString(&string_value)) {
+    OutputString(indent);
+    OutputString(string_value);
+    OutputString("\n");
+  } else if (value->GetAsBoolean(&bool_value)) {
+    OutputString(indent);
+    OutputString(bool_value ? "true" : "false");
+    OutputString("\n");
+  } else if (value->GetAsDictionary(&dict_value)) {
+    base::DictionaryValue::Iterator iter(*dict_value);
+    while (!iter.IsAtEnd()) {
+      OutputString(indent + iter.key() + "\n");
+      PrintValue(&iter.value(), indentLevel + 1);
+      iter.Advance();
+    }
+  } else if (value->GetAsInteger(&int_value)) {
+    OutputString(indent);
+    OutputString(base::IntToString(int_value));
+    OutputString("\n");
+  } else if (value->is_none()) {
+    OutputString(indent + "<null>\n");
+  }
+}
+
+// Default handler for property
+void DefaultHandler(const std::string& name,
+                    const base::Value* value,
+                    bool value_only) {
+  if (value_only) {
+    PrintValue(value, 0);
+    return;
+  }
+  OutputString("\n");
+  OutputString(name);
+  OutputString("\n");
+  PrintValue(value, 1);
+}
+
+// Specific handler for properties that need different treatment
+
+// Prints the dict in GN scope-sytle.
+void MetadataHandler(const std::string& name,
+                     const base::Value* value,
+                     bool value_only) {
+  if (value_only) {
+    PrintDictValue(value, 0, true);
+    OutputString("\n");
+    return;
+  }
+  OutputString("\n");
+  OutputString(name);
+  OutputString("\n");
+  PrintDictValue(value, 1, true);
+  OutputString("\n");
+}
+
+// Prints label and property value on one line, capitalizing the label.
+void LabelHandler(const std::string& name,
+                  const base::Value* value,
+                  bool value_only) {
+  if (value_only) {
+    PrintValue(value, 0);
+    return;
+  }
+  std::string label = name;
+  label[0] = base::ToUpperASCII(label[0]);
+  std::string string_value;
+  if (value->GetAsString(&string_value)) {
+    OutputString(name + ": ", DECORATION_YELLOW);
+    OutputString(string_value + "\n");
+  }
+}
+
+void VisibilityHandler(const std::string& name,
+                       const base::Value* value,
+                       bool value_only) {
+  if (value_only) {
+    PrintValue(value, 0);
+    return;
+  }
+  const base::ListValue* list;
+  if (value->GetAsList(&list)) {
+    if (list->empty()) {
+      base::Value str("(no visibility)");
+      DefaultHandler(name, &str, value_only);
+    } else {
+      DefaultHandler(name, value, value_only);
+    }
+  }
+}
+
+void PublicHandler(const std::string& name,
+                   const base::Value* value,
+                   bool value_only) {
+  if (value_only) {
+    PrintValue(value, 0);
+    return;
+  }
+  std::string p;
+  if (value->GetAsString(&p)) {
+    if (p == "*") {
+      base::Value str("[All headers listed in the sources are public.]");
+      DefaultHandler(name, &str, value_only);
+      return;
+    }
+  }
+  DefaultHandler(name, value, value_only);
+}
+
+void ConfigsHandler(const std::string& name,
+                    const base::Value* value,
+                    bool value_only) {
+  bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
+  if (tree)
+    DefaultHandler(name + " tree (in order applying)", value, value_only);
+  else
+    DefaultHandler(name + " (in order applying, try also --tree)", value,
+                   value_only);
+}
+
+void DepsHandler(const std::string& name,
+                 const base::Value* value,
+                 bool value_only) {
+  bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
+  bool all = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);
+  if (tree) {
+    DefaultHandler("Dependency tree", value, value_only);
+  } else {
+    if (!all) {
+      DefaultHandler(
+          "Direct dependencies "
+          "(try also \"--all\", \"--tree\", or even \"--all --tree\")",
+          value, value_only);
+    } else {
+      DefaultHandler("All recursive dependencies", value, value_only);
+    }
+  }
+}
+
+// Outputs need special processing when output patterns are present.
+void ProcessOutputs(base::DictionaryValue* target, bool files_only) {
+  base::ListValue* patterns = nullptr;
+  base::ListValue* outputs = nullptr;
+  target->GetList("output_patterns", &patterns);
+  target->GetList(variables::kOutputs, &outputs);
+
+  int indent = 0;
+  if (outputs || patterns) {
+    if (!files_only) {
+      OutputString("\noutputs\n");
+      indent = 1;
+    }
+    if (patterns) {
+      if (!files_only) {
+        OutputString("  Output patterns\n");
+        indent = 2;
+      }
+      PrintValue(patterns, indent);
+      if (!files_only)
+        OutputString("\n  Resolved output file list\n");
+    }
+    if (outputs)
+      PrintValue(outputs, indent);
+
+    target->Remove("output_patterns", nullptr);
+    target->Remove(variables::kOutputs, nullptr);
+  }
+}
+
+using DescHandlerFunc = void (*)(const std::string& name,
+                                 const base::Value* value,
+                                 bool value_only);
+std::map<std::string, DescHandlerFunc> GetHandlers() {
+  return {{"type", LabelHandler},
+          {"toolchain", LabelHandler},
+          {variables::kVisibility, VisibilityHandler},
+          {variables::kMetadata, MetadataHandler},
+          {variables::kTestonly, DefaultHandler},
+          {variables::kCheckIncludes, DefaultHandler},
+          {variables::kAllowCircularIncludesFrom, DefaultHandler},
+          {variables::kSources, DefaultHandler},
+          {variables::kPublic, PublicHandler},
+          {variables::kInputs, DefaultHandler},
+          {variables::kConfigs, ConfigsHandler},
+          {variables::kPublicConfigs, ConfigsHandler},
+          {variables::kAllDependentConfigs, ConfigsHandler},
+          {variables::kScript, DefaultHandler},
+          {variables::kArgs, DefaultHandler},
+          {variables::kDepfile, DefaultHandler},
+          {"bundle_data", DefaultHandler},
+          {variables::kArflags, DefaultHandler},
+          {variables::kAsmflags, DefaultHandler},
+          {variables::kCflags, DefaultHandler},
+          {variables::kCflagsC, DefaultHandler},
+          {variables::kCflagsCC, DefaultHandler},
+          {variables::kCflagsObjC, DefaultHandler},
+          {variables::kCflagsObjCC, DefaultHandler},
+          {variables::kDefines, DefaultHandler},
+          {variables::kFrameworkDirs, DefaultHandler},
+          {variables::kFrameworks, DefaultHandler},
+          {variables::kIncludeDirs, DefaultHandler},
+          {variables::kLdflags, DefaultHandler},
+          {variables::kPrecompiledHeader, DefaultHandler},
+          {variables::kPrecompiledSource, DefaultHandler},
+          {variables::kDeps, DepsHandler},
+          {variables::kLibs, DefaultHandler},
+          {variables::kLibDirs, DefaultHandler},
+          {variables::kDataKeys, DefaultHandler},
+          {variables::kRebase, DefaultHandler},
+          {variables::kWalkKeys, DefaultHandler},
+          {variables::kWeakFrameworks, DefaultHandler},
+          {variables::kWriteOutputConversion, DefaultHandler},
+          {variables::kRustCrateName, DefaultHandler},
+          {variables::kRustCrateRoot, DefaultHandler},
+          {variables::kSwiftModuleName, DefaultHandler},
+          {variables::kSwiftBridgeHeader, DefaultHandler},
+          {"runtime_deps", DefaultHandler}};
+}
+
+void HandleProperty(const std::string& what,
+                    const std::map<std::string, DescHandlerFunc>& handler_map,
+                    std::unique_ptr<base::Value>& v,
+                    std::unique_ptr<base::DictionaryValue>& dict) {
+  if (dict->Remove(what, &v)) {
+    auto pair = handler_map.find(what);
+    if (pair != handler_map.end())
+      pair->second(what, v.get(), false);
+  }
+}
+
+bool PrintTarget(const Target* target,
+                 const std::string& what,
+                 bool single_target,
+                 const std::map<std::string, DescHandlerFunc>& handler_map,
+                 bool all,
+                 bool tree,
+                 bool blame) {
+  std::unique_ptr<base::DictionaryValue> dict =
+      DescBuilder::DescriptionForTarget(target, what, all, tree, blame);
+  if (!what.empty() && dict->empty()) {
+    OutputString("Don't know how to display \"" + what + "\" for \"" +
+                 Target::GetStringForOutputType(target->output_type()) +
+                 "\".\n");
+    return false;
+  }
+  // Print single value
+  if (!what.empty() && dict->size() == 1 && single_target) {
+    if (what == variables::kOutputs) {
+      ProcessOutputs(dict.get(), true);
+      return true;
+    }
+    base::DictionaryValue::Iterator iter(*dict);
+    auto pair = handler_map.find(what);
+    if (pair != handler_map.end())
+      pair->second(what, &iter.value(), true);
+    return true;
+  }
+
+  OutputString("Target ", DECORATION_YELLOW);
+  OutputString(target->label().GetUserVisibleName(false));
+  OutputString("\n");
+
+  std::unique_ptr<base::Value> v;
+  // Entries with DefaultHandler are present to enforce order
+  HandleProperty("type", handler_map, v, dict);
+  HandleProperty("toolchain", handler_map, v, dict);
+  HandleProperty(variables::kSwiftModuleName, handler_map, v, dict);
+  HandleProperty(variables::kRustCrateName, handler_map, v, dict);
+  HandleProperty(variables::kRustCrateRoot, handler_map, v, dict);
+  HandleProperty(variables::kVisibility, handler_map, v, dict);
+  HandleProperty(variables::kMetadata, handler_map, v, dict);
+  HandleProperty(variables::kTestonly, handler_map, v, dict);
+  HandleProperty(variables::kCheckIncludes, handler_map, v, dict);
+  HandleProperty(variables::kAllowCircularIncludesFrom, handler_map, v, dict);
+  HandleProperty(variables::kSources, handler_map, v, dict);
+  HandleProperty(variables::kSwiftBridgeHeader, handler_map, v, dict);
+  HandleProperty(variables::kPublic, handler_map, v, dict);
+  HandleProperty(variables::kInputs, handler_map, v, dict);
+  HandleProperty(variables::kConfigs, handler_map, v, dict);
+  HandleProperty(variables::kPublicConfigs, handler_map, v, dict);
+  HandleProperty(variables::kAllDependentConfigs, handler_map, v, dict);
+  HandleProperty(variables::kScript, handler_map, v, dict);
+  HandleProperty(variables::kArgs, handler_map, v, dict);
+  HandleProperty(variables::kDepfile, handler_map, v, dict);
+  ProcessOutputs(dict.get(), false);
+  HandleProperty("bundle_data", handler_map, v, dict);
+  HandleProperty(variables::kArflags, handler_map, v, dict);
+  HandleProperty(variables::kAsmflags, handler_map, v, dict);
+  HandleProperty(variables::kCflags, handler_map, v, dict);
+  HandleProperty(variables::kCflagsC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsCC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsObjC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsObjCC, handler_map, v, dict);
+  HandleProperty(variables::kSwiftflags, handler_map, v, dict);
+  HandleProperty(variables::kDefines, handler_map, v, dict);
+  HandleProperty(variables::kFrameworkDirs, handler_map, v, dict);
+  HandleProperty(variables::kFrameworks, handler_map, v, dict);
+  HandleProperty(variables::kIncludeDirs, handler_map, v, dict);
+  HandleProperty(variables::kLdflags, handler_map, v, dict);
+  HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict);
+  HandleProperty(variables::kPrecompiledSource, handler_map, v, dict);
+  HandleProperty(variables::kDeps, handler_map, v, dict);
+  HandleProperty(variables::kLibs, handler_map, v, dict);
+  HandleProperty(variables::kLibDirs, handler_map, v, dict);
+  HandleProperty(variables::kDataKeys, handler_map, v, dict);
+  HandleProperty(variables::kRebase, handler_map, v, dict);
+  HandleProperty(variables::kWalkKeys, handler_map, v, dict);
+  HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
+  HandleProperty(variables::kWriteOutputConversion, handler_map, v, dict);
+
+#undef HandleProperty
+
+  // Process the rest (if any)
+  base::DictionaryValue::Iterator iter(*dict);
+  while (!iter.IsAtEnd()) {
+    DefaultHandler(iter.key(), &iter.value(), false);
+    iter.Advance();
+  }
+
+  return true;
+}
+
+bool PrintConfig(const Config* config,
+                 const std::string& what,
+                 bool single_config,
+                 const std::map<std::string, DescHandlerFunc>& handler_map) {
+  std::unique_ptr<base::DictionaryValue> dict =
+      DescBuilder::DescriptionForConfig(config, what);
+  if (!what.empty() && dict->empty()) {
+    OutputString("Don't know how to display \"" + what + "\" for a config.\n");
+    return false;
+  }
+  // Print single value
+  if (!what.empty() && dict->size() == 1 && single_config) {
+    base::DictionaryValue::Iterator iter(*dict);
+    auto pair = handler_map.find(what);
+    if (pair != handler_map.end())
+      pair->second(what, &iter.value(), true);
+    return true;
+  }
+
+  OutputString("Config: ", DECORATION_YELLOW);
+  OutputString(config->label().GetUserVisibleName(false));
+  OutputString("\n");
+
+  std::unique_ptr<base::Value> v;
+  HandleProperty("toolchain", handler_map, v, dict);
+  if (!config->configs().empty()) {
+    OutputString(
+        "(This is a composite config, the values below are after the\n"
+        "expansion of the child configs.)\n");
+  }
+  HandleProperty(variables::kArflags, handler_map, v, dict);
+  HandleProperty(variables::kAsmflags, handler_map, v, dict);
+  HandleProperty(variables::kCflags, handler_map, v, dict);
+  HandleProperty(variables::kCflagsC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsCC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsObjC, handler_map, v, dict);
+  HandleProperty(variables::kCflagsObjCC, handler_map, v, dict);
+  HandleProperty(variables::kSwiftflags, handler_map, v, dict);
+  HandleProperty(variables::kDefines, handler_map, v, dict);
+  HandleProperty(variables::kFrameworkDirs, handler_map, v, dict);
+  HandleProperty(variables::kFrameworks, handler_map, v, dict);
+  HandleProperty(variables::kIncludeDirs, handler_map, v, dict);
+  HandleProperty(variables::kInputs, handler_map, v, dict);
+  HandleProperty(variables::kLdflags, handler_map, v, dict);
+  HandleProperty(variables::kLibs, handler_map, v, dict);
+  HandleProperty(variables::kLibDirs, handler_map, v, dict);
+  HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict);
+  HandleProperty(variables::kPrecompiledSource, handler_map, v, dict);
+  HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
+
+#undef HandleProperty
+
+  return true;
+}
+
+}  // namespace
+
+// desc ------------------------------------------------------------------------
+
+const char kDesc[] = "desc";
+const char kDesc_HelpShort[] =
+    "desc: Show lots of insightful information about a target or config.";
+const char kDesc_Help[] =
+    R"(gn desc
+
+  gn desc <out_dir> <label or pattern> [<what to show>] [--blame]
+          [--format=json]
+
+  Displays information about a given target or config. The build parameters
+  will be taken for the build in the given <out_dir>.
+
+  The <label or pattern> can be a target label, a config label, or a label
+  pattern (see "gn help label_pattern"). A label pattern will only match
+  targets.
+
+Possibilities for <what to show>
+
+  (If unspecified an overall summary will be displayed.)
+
+  all_dependent_configs
+  allow_circular_includes_from
+  arflags [--blame]
+  args
+  cflags [--blame]
+  cflags_c [--blame]
+  cflags_cc [--blame]
+  check_includes
+  configs [--tree] (see below)
+  data_keys
+  defines [--blame]
+  depfile
+  deps [--all] [--tree] (see below)
+  framework_dirs
+  frameworks
+  include_dirs [--blame]
+  inputs
+  ldflags [--blame]
+  lib_dirs
+  libs
+  metadata
+  output_conversion
+  outputs
+  public_configs
+  public
+  rebase
+  script
+  sources
+  testonly
+  visibility
+  walk_keys
+  weak_frameworks
+
+  runtime_deps
+      Compute all runtime deps for the given target. This is a computed list
+      and does not correspond to any GN variable, unlike most other values
+      here.
+
+      The output is a list of file names relative to the build directory. See
+      "gn help runtime_deps" for how this is computed. This also works with
+      "--blame" to see the source of the dependency.
+
+Shared flags
+
+)"
+
+    DEFAULT_TOOLCHAIN_SWITCH_HELP
+
+    R"(
+  --format=json
+      Format the output as JSON instead of text.
+
+Target flags
+
+  --blame
+      Used with any value specified on a config, this will name the config that
+      causes that target to get the flag. This doesn't currently work for libs,
+      lib_dirs, frameworks, weak_frameworks and framework_dirs because those are
+      inherited and are more complicated to figure out the blame (patches
+      welcome).
+
+Configs
+
+  The "configs" section will list all configs that apply. For targets this will
+  include configs specified in the "configs" variable of the target, and also
+  configs pushed onto this target via public or "all dependent" configs.
+
+  Configs can have child configs. Specifying --tree will show the hierarchy.
+
+Printing outputs
+
+  The "outputs" section will list all outputs that apply, including the outputs
+  computed from the tool definition (eg for "executable", "static_library", ...
+  targets).
+
+Printing deps
+
+  Deps will include all public, private, and data deps (TODO this could be
+  clarified and enhanced) sorted in order applying. The following may be used:
+
+  --all
+      Collects all recursive dependencies and prints a sorted flat list. Also
+      usable with --tree (see below).
+
+)"
+
+    TARGET_PRINTING_MODE_COMMAND_LINE_HELP
+    "\n" TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
+
+    R"(
+  --tree
+      Print a dependency tree. By default, duplicates will be elided with "..."
+      but when --all and -tree are used together, no eliding will be performed.
+
+      The "deps", "public_deps", and "data_deps" will all be included in the
+      tree.
+
+      Tree output can not be used with the filtering or output flags: --as,
+      --type, --testonly.
+
+)"
+
+    TARGET_TYPE_FILTER_COMMAND_LINE_HELP
+
+    R"(
+Note
+
+  This command will show the full name of directories and source files, but
+  when directories and source paths are written to the build file, they will be
+  adjusted to be relative to the build directory. So the values for paths
+  displayed by this command won't match (but should mean the same thing).
+
+Examples
+
+  gn desc out/Debug //base:base
+      Summarizes the given target.
+
+  gn desc out/Foo :base_unittests deps --tree
+      Shows a dependency tree of the "base_unittests" project in
+      the current directory.
+
+  gn desc out/Debug //base defines --blame
+      Shows defines set for the //base:base target, annotated by where
+      each one was set from.
+)";
+
+int RunDesc(const std::vector<std::string>& args) {
+  if (args.size() != 2 && args.size() != 3) {
+    Err(Location(), "Unknown command format. See \"gn help desc\"",
+        "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"")
+        .PrintToStdout();
+    return 1;
+  }
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false))
+    return 1;
+  if (!setup->Run())
+    return 1;
+
+  // Resolve target(s) and config from inputs.
+  UniqueVector<const Target*> target_matches;
+  UniqueVector<const Config*> config_matches;
+  UniqueVector<const Toolchain*> toolchain_matches;
+  UniqueVector<SourceFile> file_matches;
+
+  std::vector<std::string> target_list;
+  target_list.push_back(args[1]);
+
+  if (!ResolveFromCommandLineInput(
+          setup, target_list, cmdline->HasSwitch(switches::kDefaultToolchain),
+          &target_matches, &config_matches, &toolchain_matches, &file_matches))
+    return 1;
+
+  std::string what_to_print;
+  if (args.size() == 3)
+    what_to_print = args[2];
+
+  bool json = cmdline->GetSwitchValueASCII("format") == "json";
+
+  if (target_matches.empty() && config_matches.empty()) {
+    OutputString(
+        "The input " + args[1] + " matches no targets, configs or files.\n",
+        DECORATION_YELLOW);
+    return 1;
+  }
+
+  if (json) {
+    // Convert all targets/configs to JSON, serialize and print them
+    auto res = std::make_unique<base::DictionaryValue>();
+    if (!target_matches.empty()) {
+      for (const auto* target : target_matches) {
+        res->SetWithoutPathExpansion(
+            target->label().GetUserVisibleName(
+                target->settings()->default_toolchain_label()),
+            DescBuilder::DescriptionForTarget(
+                target, what_to_print, cmdline->HasSwitch(kAll),
+                cmdline->HasSwitch(kTree), cmdline->HasSwitch(kBlame)));
+      }
+    } else if (!config_matches.empty()) {
+      for (const auto* config : config_matches) {
+        res->SetWithoutPathExpansion(
+            config->label().GetUserVisibleName(false),
+            DescBuilder::DescriptionForConfig(config, what_to_print));
+      }
+    }
+    std::string s;
+    base::JSONWriter::WriteWithOptions(
+        *res.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
+    OutputString(s);
+  } else {
+    // Regular (non-json) formatted output
+    bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1;
+    std::map<std::string, DescHandlerFunc> handlers = GetHandlers();
+
+    bool printed_output = false;
+    for (const Target* target : target_matches) {
+      if (printed_output)
+        OutputString("\n\n");
+      printed_output = true;
+
+      if (!PrintTarget(target, what_to_print, !multiple_outputs, handlers,
+                       cmdline->HasSwitch(kAll), cmdline->HasSwitch(kTree),
+                       cmdline->HasSwitch(kBlame)))
+        return 1;
+    }
+    for (const Config* config : config_matches) {
+      if (printed_output)
+        OutputString("\n\n");
+      printed_output = true;
+
+      if (!PrintConfig(config, what_to_print, !multiple_outputs, handlers))
+        return 1;
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_format.cc b/src/gn/command_format.cc
new file mode 100644 (file)
index 0000000..e94fb4f
--- /dev/null
@@ -0,0 +1,1437 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/command_format.h"
+
+#include <stddef.h>
+
+#include <sstream>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "gn/commands.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file.h"
+#include "gn/parser.h"
+#include "gn/scheduler.h"
+#include "gn/setup.h"
+#include "gn/source_file.h"
+#include "gn/string_utils.h"
+#include "gn/switches.h"
+#include "gn/tokenizer.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+namespace commands {
+
+const char kSwitchDryRun[] = "dry-run";
+const char kSwitchDumpTree[] = "dump-tree";
+const char kSwitchReadTree[] = "read-tree";
+const char kSwitchStdin[] = "stdin";
+const char kSwitchTreeTypeJSON[] = "json";
+const char kSwitchTreeTypeText[] = "text";
+
+const char kFormat[] = "format";
+const char kFormat_HelpShort[] = "format: Format .gn files.";
+const char kFormat_Help[] =
+    R"(gn format [--dump-tree] (--stdin | <list of build_files...>)
+
+  Formats .gn file to a standard format.
+
+  The contents of some lists ('sources', 'deps', etc.) will be sorted to a
+  canonical order. To suppress this, you can add a comment of the form "#
+  NOSORT" immediately preceding the assignment. e.g.
+
+  # NOSORT
+  sources = [
+    "z.cc",
+    "a.cc",
+  ]
+
+Arguments
+
+  --dry-run
+      Prints the list of files that would be reformatted but does not write
+      anything to disk. This is useful for presubmit/lint-type checks.
+      - Exit code 0: successful format, matches on disk.
+      - Exit code 1: general failure (parse error, etc.)
+      - Exit code 2: successful format, but differs from on disk.
+
+  --dump-tree[=( text | json )]
+      Dumps the parse tree to stdout and does not update the file or print
+      formatted output. If no format is specified, text format will be used.
+
+  --stdin
+      Read input from stdin and write to stdout rather than update a file
+      in-place.
+
+  --read-tree=json
+      Reads an AST from stdin in the format output by --dump-tree=json and
+      uses that as the parse tree. (The only read-tree format currently
+      supported is json.) The given .gn file will be overwritten. This can be
+      used to programmatically transform .gn files.
+
+Examples
+  gn format //some/BUILD.gn //some/other/BUILD.gn //and/another/BUILD.gn
+  gn format some\\BUILD.gn
+  gn format /abspath/some/BUILD.gn
+  gn format --stdin
+  gn format --read-tree=json //rewritten/BUILD.gn
+)";
+
+namespace {
+
+const int kIndentSize = 2;
+const int kMaximumWidth = 80;
+
+const int kPenaltyLineBreak = 500;
+const int kPenaltyHorizontalSeparation = 100;
+const int kPenaltyExcess = 10000;
+const int kPenaltyBrokenLineOnOneLiner = 5000;
+
+enum Precedence {
+  kPrecedenceLowest,
+  kPrecedenceAssign,
+  kPrecedenceOr,
+  kPrecedenceAnd,
+  kPrecedenceCompare,
+  kPrecedenceAdd,
+  kPrecedenceUnary,
+  kPrecedenceSuffix,
+};
+
+int CountLines(const std::string& str) {
+  return static_cast<int>(base::SplitStringPiece(str, "\n",
+                                                 base::KEEP_WHITESPACE,
+                                                 base::SPLIT_WANT_ALL)
+                              .size());
+}
+
+class Printer {
+ public:
+  Printer();
+  ~Printer();
+
+  void Block(const ParseNode* file);
+
+  std::string String() const { return output_; }
+
+ private:
+  // Format a list of values using the given style.
+  enum SequenceStyle {
+    kSequenceStyleList,
+    kSequenceStyleBracedBlock,
+    kSequenceStyleBracedBlockAlreadyOpen,
+  };
+
+  struct Metrics {
+    Metrics() : first_length(-1), longest_length(-1), multiline(false) {}
+    int first_length;
+    int longest_length;
+    bool multiline;
+  };
+
+  // Add to output.
+  void Print(std::string_view str);
+
+  // Add the current margin (as spaces) to the output.
+  void PrintMargin();
+
+  void TrimAndPrintToken(const Token& token);
+
+  void PrintTrailingCommentsWrapped(const std::vector<Token>& comments);
+
+  void FlushComments();
+
+  void PrintSuffixComments(const ParseNode* node);
+
+  // End the current line, flushing end of line comments.
+  void Newline();
+
+  // Remove trailing spaces from the current line.
+  void Trim();
+
+  // Whether there's a blank separator line at the current position.
+  bool HaveBlankLine();
+
+  // Sort a list on the RHS if the LHS is one of the following:
+  // 'sources': sorted alphabetically.
+  // 'deps' or ends in 'deps': sorted such that relative targets are first,
+  //   followed by global targets, each internally sorted alphabetically.
+  // 'visibility': same as 'deps'.
+  void SortIfApplicable(const BinaryOpNode* binop);
+
+  // Sort contiguous import() function calls in the given ordered list of
+  // statements (the body of a block or scope).
+  template <class PARSENODE>
+  void SortImports(std::vector<std::unique_ptr<PARSENODE>>& statements);
+
+  // Heuristics to decide if there should be a blank line added between two
+  // items. For various "small" items, it doesn't look nice if there's too much
+  // vertical whitespace added.
+  bool ShouldAddBlankLineInBetween(const ParseNode* a, const ParseNode* b);
+
+  // Get the 0-based x position on the current line.
+  int CurrentColumn() const;
+
+  // Get the current line in the output;
+  int CurrentLine() const;
+
+  // Adds an opening ( if prec is less than the outers (to maintain evalution
+  // order for a subexpression). If an opening paren is emitted, *parenthesized
+  // will be set so it can be closed at the end of the expression.
+  void AddParen(int prec, int outer_prec, bool* parenthesized);
+
+  // Print the expression given by |root| to the output buffer and appends
+  // |suffix| to that output. Returns a penalty that represents the cost of
+  // adding that output to the buffer (where higher is worse). The value of
+  // outer_prec gives the precedence of the operator outside this Expr. If that
+  // operator binds tighter than root's, Expr() must introduce parentheses.
+  int Expr(const ParseNode* root, int outer_prec, const std::string& suffix);
+
+  // Generic penalties for exceeding maximum width, adding more lines, etc.
+  int AssessPenalty(const std::string& output);
+
+  // Tests if any lines exceed the maximum width.
+  bool ExceedsMaximumWidth(const std::string& output);
+
+  // Format a list of values using the given style.
+  // |end| holds any trailing comments to be printed just before the closing
+  // bracket.
+  template <class PARSENODE>  // Just for const covariance.
+  void Sequence(SequenceStyle style,
+                const std::vector<std::unique_ptr<PARSENODE>>& list,
+                const ParseNode* end,
+                bool force_multiline);
+
+  // Returns the penalty.
+  int FunctionCall(const FunctionCallNode* func_call,
+                   const std::string& suffix);
+
+  // Create a clone of this Printer in a similar state (other than the output,
+  // but including margins, etc.) to be used for dry run measurements.
+  void InitializeSub(Printer* sub);
+
+  template <class PARSENODE>
+  bool ListWillBeMultiline(const std::vector<std::unique_ptr<PARSENODE>>& list,
+                           const ParseNode* end);
+
+  std::string output_;           // Output buffer.
+  std::vector<Token> comments_;  // Pending end-of-line comments.
+  int margin() const { return stack_.back().margin; }
+
+  int penalty_depth_;
+  int GetPenaltyForLineBreak() const {
+    return penalty_depth_ * kPenaltyLineBreak;
+  }
+
+  struct IndentState {
+    IndentState()
+        : margin(0),
+          continuation_requires_indent(false),
+          parent_is_boolean_or(false) {}
+    IndentState(int margin,
+                bool continuation_requires_indent,
+                bool parent_is_boolean_or)
+        : margin(margin),
+          continuation_requires_indent(continuation_requires_indent),
+          parent_is_boolean_or(parent_is_boolean_or) {}
+
+    // The left margin (number of spaces).
+    int margin;
+
+    bool continuation_requires_indent;
+
+    bool parent_is_boolean_or;
+  };
+  // Stack used to track
+  std::vector<IndentState> stack_;
+
+  // Gives the precedence for operators in a BinaryOpNode.
+  std::map<std::string_view, Precedence> precedence_;
+
+  DISALLOW_COPY_AND_ASSIGN(Printer);
+};
+
+Printer::Printer() : penalty_depth_(0) {
+  output_.reserve(100 << 10);
+  precedence_["="] = kPrecedenceAssign;
+  precedence_["+="] = kPrecedenceAssign;
+  precedence_["-="] = kPrecedenceAssign;
+  precedence_["||"] = kPrecedenceOr;
+  precedence_["&&"] = kPrecedenceAnd;
+  precedence_["<"] = kPrecedenceCompare;
+  precedence_[">"] = kPrecedenceCompare;
+  precedence_["=="] = kPrecedenceCompare;
+  precedence_["!="] = kPrecedenceCompare;
+  precedence_["<="] = kPrecedenceCompare;
+  precedence_[">="] = kPrecedenceCompare;
+  precedence_["+"] = kPrecedenceAdd;
+  precedence_["-"] = kPrecedenceAdd;
+  precedence_["!"] = kPrecedenceUnary;
+  stack_.push_back(IndentState());
+}
+
+Printer::~Printer() = default;
+
+void Printer::Print(std::string_view str) {
+  output_.append(str);
+}
+
+void Printer::PrintMargin() {
+  output_ += std::string(margin(), ' ');
+}
+
+void Printer::TrimAndPrintToken(const Token& token) {
+  std::string trimmed;
+  TrimWhitespaceASCII(std::string(token.value()), base::TRIM_ALL, &trimmed);
+  Print(trimmed);
+}
+
+// Assumes that the margin is set to the indent level where the comments should
+// be aligned. This doesn't de-wrap, it only wraps. So if a suffix comment
+// causes the line to exceed 80 col it will be wrapped, but the subsequent line
+// would fit on the then-broken line it will not be merged with it. This is
+// partly because it's difficult to implement at this level, but also because
+// it can break hand-authored line breaks where they're starting a new paragraph
+// or statement.
+void Printer::PrintTrailingCommentsWrapped(const std::vector<Token>& comments) {
+  bool have_empty_line = true;
+  auto start_next_line = [this, &have_empty_line]() {
+    Trim();
+    Print("\n");
+    PrintMargin();
+    have_empty_line = true;
+  };
+  for (const auto& c : comments) {
+    if (!have_empty_line) {
+      start_next_line();
+    }
+
+    std::string trimmed;
+    TrimWhitespaceASCII(std::string(c.value()), base::TRIM_ALL, &trimmed);
+
+    if (margin() + trimmed.size() <= kMaximumWidth) {
+      Print(trimmed);
+      have_empty_line = false;
+    } else {
+      bool continuation = false;
+      std::vector<std::string> split_on_spaces = base::SplitString(
+          c.value(), " ", base::WhitespaceHandling::TRIM_WHITESPACE,
+          base::SplitResult::SPLIT_WANT_NONEMPTY);
+      for (size_t j = 0; j < split_on_spaces.size(); ++j) {
+        if (have_empty_line && continuation) {
+          Print("# ");
+        }
+        Print(split_on_spaces[j]);
+        Print(" ");
+        if (split_on_spaces[j] != "#") {
+          have_empty_line = false;
+        }
+        if (!have_empty_line &&
+            (j < split_on_spaces.size() - 1 &&
+             CurrentColumn() + split_on_spaces[j + 1].size() > kMaximumWidth)) {
+          start_next_line();
+          continuation = true;
+        }
+      }
+    }
+  }
+}
+
+// Used during penalty evaluation, similar to Newline().
+void Printer::PrintSuffixComments(const ParseNode* node) {
+  if (node->comments() && !node->comments()->suffix().empty()) {
+    Print("  ");
+    stack_.push_back(IndentState(CurrentColumn(), false, false));
+    PrintTrailingCommentsWrapped(node->comments()->suffix());
+    stack_.pop_back();
+  }
+}
+
+void Printer::FlushComments() {
+  if (!comments_.empty()) {
+    Print("  ");
+    // Save the margin, and temporarily set it to where the first comment
+    // starts so that multiple suffix comments are vertically aligned.
+    stack_.push_back(IndentState(CurrentColumn(), false, false));
+    PrintTrailingCommentsWrapped(comments_);
+    stack_.pop_back();
+    comments_.clear();
+  }
+}
+
+void Printer::Newline() {
+  FlushComments();
+  Trim();
+  Print("\n");
+  PrintMargin();
+}
+
+void Printer::Trim() {
+  size_t n = output_.size();
+  while (n > 0 && output_[n - 1] == ' ')
+    --n;
+  output_.resize(n);
+}
+
+bool Printer::HaveBlankLine() {
+  size_t n = output_.size();
+  while (n > 0 && output_[n - 1] == ' ')
+    --n;
+  return n > 2 && output_[n - 1] == '\n' && output_[n - 2] == '\n';
+}
+
+void Printer::SortIfApplicable(const BinaryOpNode* binop) {
+  if (const Comments* comments = binop->comments()) {
+    const std::vector<Token>& before = comments->before();
+    if (!before.empty() && (before.front().value() == "# NOSORT" ||
+                            before.back().value() == "# NOSORT")) {
+      // Allow disabling of sort for specific actions that might be
+      // order-sensitive.
+      return;
+    }
+  }
+  const IdentifierNode* ident = binop->left()->AsIdentifier();
+  const ListNode* list = binop->right()->AsList();
+  if ((binop->op().value() == "=" || binop->op().value() == "+=" ||
+       binop->op().value() == "-=") &&
+      ident && list) {
+    const std::string_view lhs = ident->value().value();
+    if (base::EndsWith(lhs, "sources", base::CompareCase::SENSITIVE) ||
+        lhs == "public")
+      const_cast<ListNode*>(list)->SortAsStringsList();
+    else if (base::EndsWith(lhs, "deps", base::CompareCase::SENSITIVE) ||
+             lhs == "visibility")
+      const_cast<ListNode*>(list)->SortAsTargetsList();
+  }
+}
+
+template <class PARSENODE>
+void Printer::SortImports(std::vector<std::unique_ptr<PARSENODE>>& statements) {
+  // Build a set of ranges by indices of FunctionCallNode's that are imports.
+
+  std::vector<std::vector<size_t>> import_statements;
+
+  auto is_import = [](const PARSENODE* p) {
+    const FunctionCallNode* func_call = p->AsFunctionCall();
+    return func_call && func_call->function().value() == "import";
+  };
+
+  std::vector<size_t> current_group;
+  for (size_t i = 0; i < statements.size(); ++i) {
+    if (is_import(statements[i].get())) {
+      if (i > 0 && (!is_import(statements[i - 1].get()) ||
+                    ShouldAddBlankLineInBetween(statements[i - 1].get(),
+                                                statements[i].get()))) {
+        if (!current_group.empty()) {
+          import_statements.push_back(current_group);
+          current_group.clear();
+        }
+      }
+      current_group.push_back(i);
+    }
+  }
+
+  if (!current_group.empty())
+    import_statements.push_back(current_group);
+
+  struct CompareByImportFile {
+    bool operator()(const std::unique_ptr<PARSENODE>& a,
+                    const std::unique_ptr<PARSENODE>& b) const {
+      const auto& a_args = a->AsFunctionCall()->args()->contents();
+      const auto& b_args = b->AsFunctionCall()->args()->contents();
+      std::string_view a_name;
+      std::string_view b_name;
+
+      // Non-literal imports are treated as empty names, and order is
+      // maintained. Arbitrarily complex expressions in import() are
+      // rare, and it probably doesn't make sense to sort non-string
+      // literals anyway, see format_test_data/083.gn.
+      if (!a_args.empty() && a_args[0]->AsLiteral())
+        a_name = a_args[0]->AsLiteral()->value().value();
+      if (!b_args.empty() && b_args[0]->AsLiteral())
+        b_name = b_args[0]->AsLiteral()->value().value();
+
+      auto is_absolute = [](std::string_view import) {
+        return import.size() >= 3 && import[0] == '"' && import[1] == '/' &&
+               import[2] == '/';
+      };
+      int a_is_rel = !is_absolute(a_name);
+      int b_is_rel = !is_absolute(b_name);
+
+      return std::tie(a_is_rel, a_name) < std::tie(b_is_rel, b_name);
+    }
+  };
+
+  int line_after_previous = -1;
+
+  for (const auto& group : import_statements) {
+    size_t begin = group[0];
+    size_t end = group.back() + 1;
+
+    // Save the original line number so that ranges can be re-assigned. They're
+    // contiguous because of the partitioning code above. Later formatting
+    // relies on correct line number to know whether to insert blank lines,
+    // which is why these need to be fixed up. Additionally, to handle multiple
+    // imports on one line, they're assigned sequential line numbers, and
+    // subsequent blocks will be gapped from them.
+    int start_line =
+        std::max(statements[begin]->GetRange().begin().line_number(),
+                 line_after_previous + 1);
+
+    std::sort(statements.begin() + begin, statements.begin() + end,
+              CompareByImportFile());
+
+    const PARSENODE* prev = nullptr;
+    for (size_t i = begin; i < end; ++i) {
+      const PARSENODE* node = statements[i].get();
+      int line_number =
+          prev ? prev->GetRange().end().line_number() + 1 : start_line;
+      if (node->comments() && !node->comments()->before().empty())
+        line_number++;
+      const_cast<FunctionCallNode*>(node->AsFunctionCall())
+          ->SetNewLocation(line_number);
+      prev = node;
+      line_after_previous = line_number + 1;
+    }
+  }
+}
+
+namespace {
+
+int SuffixCommentTreeWalk(const ParseNode* node) {
+  // Check all the children for suffix comments. This is conceptually simple,
+  // but ugly as there's not a generic parse tree walker. This walker goes
+  // lowest child first so that if it's valid that's returned.
+  if (!node)
+    return -1;
+
+#define RETURN_IF_SET(x)             \
+  if (int result = (x); result >= 0) \
+    return result;
+
+  if (const AccessorNode* accessor = node->AsAccessor()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(accessor->subscript()));
+    RETURN_IF_SET(SuffixCommentTreeWalk(accessor->member()));
+  } else if (const BinaryOpNode* binop = node->AsBinaryOp()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(binop->right()));
+  } else if (const BlockNode* block = node->AsBlock()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(block->End()));
+  } else if (const ConditionNode* condition = node->AsCondition()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(condition->if_false()));
+    RETURN_IF_SET(SuffixCommentTreeWalk(condition->if_true()));
+    RETURN_IF_SET(SuffixCommentTreeWalk(condition->condition()));
+  } else if (const FunctionCallNode* func_call = node->AsFunctionCall()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(func_call->block()));
+    RETURN_IF_SET(SuffixCommentTreeWalk(func_call->args()));
+  } else if (node->AsIdentifier()) {
+    // Nothing.
+  } else if (const ListNode* list = node->AsList()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(list->End()));
+  } else if (node->AsLiteral()) {
+    // Nothing.
+  } else if (const UnaryOpNode* unaryop = node->AsUnaryOp()) {
+    RETURN_IF_SET(SuffixCommentTreeWalk(unaryop->operand()));
+  } else if (node->AsBlockComment()) {
+    // Nothing.
+  } else if (node->AsEnd()) {
+    // Nothing.
+  } else {
+    CHECK(false) << "Unhandled case in SuffixCommentTreeWalk.";
+  }
+
+#undef RETURN_IF_SET
+
+  // Check this node if there are no child comments.
+  if (node->comments() && !node->comments()->suffix().empty()) {
+    return node->comments()->suffix().back().location().line_number();
+  }
+
+  return -1;
+};
+
+// If there are suffix comments on the first node or its children, they might
+// carry down multiple lines. Otherwise, use the node's normal end range. This
+// function is needed because the parse tree doesn't include comments in the
+// location ranges, and it's not a straightforword change to add them. So this
+// is effectively finding the "real" range for |root| including suffix comments.
+// Note that it's not enough to simply look at |root|'s suffix comments because
+// in the case of:
+//
+//   a =
+//       b + c  # something
+//              # or other
+//   x = y
+//
+// the comments are attached to a BinOp+ which is a child of BinOp=, not
+// directly to the BinOp= which will be what's being used to determine if there
+// should be a blank line inserted before the |x| line.
+int FindLowestSuffixComment(const ParseNode* root) {
+  LocationRange range = root->GetRange();
+  int end = range.end().line_number();
+  int result = SuffixCommentTreeWalk(root);
+  return (result == -1 || result < end) ? end : result;
+}
+
+}  // namespace
+
+bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a,
+                                          const ParseNode* b) {
+  LocationRange b_range = b->GetRange();
+  int a_end = FindLowestSuffixComment(a);
+
+  // If they're already separated by 1 or more lines, then we want to keep a
+  // blank line.
+  return (b_range.begin().line_number() > a_end + 1) ||
+         // Always put a blank line before a block comment.
+         b->AsBlockComment();
+}
+
+int Printer::CurrentColumn() const {
+  int n = 0;
+  while (n < static_cast<int>(output_.size()) &&
+         output_[output_.size() - 1 - n] != '\n') {
+    ++n;
+  }
+  return n;
+}
+
+int Printer::CurrentLine() const {
+  int count = 1;
+  for (const char* p = output_.c_str(); (p = strchr(p, '\n')) != nullptr;) {
+    ++count;
+    ++p;
+  }
+  return count;
+}
+
+void Printer::Block(const ParseNode* root) {
+  const BlockNode* block = root->AsBlock();
+
+  if (block->comments()) {
+    for (const auto& c : block->comments()->before()) {
+      TrimAndPrintToken(c);
+      Newline();
+    }
+  }
+
+  SortImports(const_cast<std::vector<std::unique_ptr<ParseNode>>&>(
+      block->statements()));
+
+  size_t i = 0;
+  for (const auto& stmt : block->statements()) {
+    Expr(stmt.get(), kPrecedenceLowest, std::string());
+    Newline();
+    if (stmt->comments()) {
+      // Why are before() not printed here too? before() are handled inside
+      // Expr(), as are suffix() which are queued to the next Newline().
+      // However, because it's a general expression handler, it doesn't insert
+      // the newline itself, which only happens between block statements. So,
+      // the after are handled explicitly here.
+      for (const auto& c : stmt->comments()->after()) {
+        TrimAndPrintToken(c);
+        Newline();
+      }
+    }
+    if (i < block->statements().size() - 1 &&
+        (ShouldAddBlankLineInBetween(block->statements()[i].get(),
+                                     block->statements()[i + 1].get()))) {
+      Newline();
+    }
+    ++i;
+  }
+
+  if (block->comments()) {
+    if (!block->statements().empty() &&
+        block->statements().back()->AsBlockComment()) {
+      // If the block ends in a comment, and there's a comment following it,
+      // then the two comments were originally separate, so keep them that way.
+      Newline();
+    }
+    for (const auto& c : block->comments()->after()) {
+      TrimAndPrintToken(c);
+      Newline();
+    }
+  }
+}
+
+int Printer::AssessPenalty(const std::string& output) {
+  int penalty = 0;
+  std::vector<std::string> lines = base::SplitString(
+      output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  penalty += static_cast<int>(lines.size() - 1) * GetPenaltyForLineBreak();
+  for (const auto& line : lines) {
+    if (line.size() > kMaximumWidth)
+      penalty += static_cast<int>(line.size() - kMaximumWidth) * kPenaltyExcess;
+  }
+  return penalty;
+}
+
+bool Printer::ExceedsMaximumWidth(const std::string& output) {
+  for (const auto& line : base::SplitString(output, "\n", base::KEEP_WHITESPACE,
+                                            base::SPLIT_WANT_ALL)) {
+    std::string_view trimmed =
+        TrimString(line, " ", base::TrimPositions::TRIM_TRAILING);
+    if (trimmed.size() > kMaximumWidth) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void Printer::AddParen(int prec, int outer_prec, bool* parenthesized) {
+  if (prec < outer_prec) {
+    Print("(");
+    *parenthesized = true;
+  }
+}
+
+int Printer::Expr(const ParseNode* root,
+                  int outer_prec,
+                  const std::string& suffix) {
+  std::string at_end = suffix;
+  int penalty = 0;
+  penalty_depth_++;
+
+  if (root->comments()) {
+    if (!root->comments()->before().empty()) {
+      Trim();
+      // If there's already other text on the line, start a new line.
+      if (CurrentColumn() > 0)
+        Print("\n");
+      // We're printing a line comment, so we need to be at the current margin.
+      PrintMargin();
+      for (const auto& c : root->comments()->before()) {
+        TrimAndPrintToken(c);
+        Newline();
+      }
+    }
+  }
+
+  bool parenthesized = false;
+
+  if (const AccessorNode* accessor = root->AsAccessor()) {
+    AddParen(kPrecedenceSuffix, outer_prec, &parenthesized);
+    Print(accessor->base().value());
+    if (accessor->member()) {
+      Print(".");
+      Expr(accessor->member(), kPrecedenceLowest, std::string());
+    } else {
+      CHECK(accessor->subscript());
+      Print("[");
+      Expr(accessor->subscript(), kPrecedenceLowest, "]");
+    }
+  } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
+    CHECK(precedence_.find(binop->op().value()) != precedence_.end());
+
+    SortIfApplicable(binop);
+
+    Precedence prec = precedence_[binop->op().value()];
+
+    // Since binary operators format left-to-right, it is ok for the left side
+    // use the same operator without parentheses, so the left uses prec. For the
+    // same reason, the right side cannot reuse the same operator, or else "x +
+    // (y + z)" would format as "x + y + z" which means "(x + y) + z". So, treat
+    // the right expression as appearing one precedence level higher.
+    // However, because the source parens are not in the parse tree, as a
+    // special case for && and || we insert strictly-redundant-but-helpful-for-
+    // human-readers parentheses.
+    int prec_left = prec;
+    int prec_right = prec + 1;
+    if (binop->op().value() == "&&" && stack_.back().parent_is_boolean_or) {
+      Print("(");
+      parenthesized = true;
+    } else {
+      AddParen(prec_left, outer_prec, &parenthesized);
+    }
+
+    if (parenthesized)
+      at_end = ")" + at_end;
+
+    int start_line = CurrentLine();
+    int start_column = CurrentColumn();
+    bool is_assignment = binop->op().value() == "=" ||
+                         binop->op().value() == "+=" ||
+                         binop->op().value() == "-=";
+
+    int indent_column = start_column;
+    if (is_assignment) {
+      // Default to a double-indent for wrapped assignments.
+      indent_column = margin() + kIndentSize * 2;
+
+      // A special case for the long lists and scope assignments that are
+      // common in .gn files, don't indent them + 4, even though they're just
+      // continuations when they're simple lists like "x = [ a, b, c, ... ]" or
+      // scopes like "x = { a = 1 b = 2 }". Put back to "normal" indenting.
+      if (const ListNode* right_as_list = binop->right()->AsList()) {
+        if (ListWillBeMultiline(right_as_list->contents(),
+                                right_as_list->End()))
+          indent_column = start_column;
+      } else {
+        if (binop->right()->AsBlock())
+          indent_column = start_column;
+      }
+    }
+    if (stack_.back().continuation_requires_indent)
+      indent_column += kIndentSize * 2;
+
+    stack_.push_back(IndentState(indent_column,
+                                 stack_.back().continuation_requires_indent,
+                                 binop->op().value() == "||"));
+    Printer sub_left;
+    InitializeSub(&sub_left);
+    sub_left.Expr(binop->left(), prec_left,
+                  std::string(" ") + std::string(binop->op().value()));
+    bool left_is_multiline = CountLines(sub_left.String()) > 1;
+    // Avoid walking the whole left redundantly times (see timing of Format.046)
+    // so pull the output and comments from subprinter.
+    Print(sub_left.String().substr(start_column));
+    std::copy(sub_left.comments_.begin(), sub_left.comments_.end(),
+              std::back_inserter(comments_));
+
+    // Single line.
+    Printer sub1;
+    InitializeSub(&sub1);
+    sub1.Print(" ");
+    int penalty_current_line = sub1.Expr(binop->right(), prec_right, at_end);
+    sub1.PrintSuffixComments(root);
+    sub1.FlushComments();
+    penalty_current_line += AssessPenalty(sub1.String());
+    if (!is_assignment && left_is_multiline) {
+      // In e.g. xxx + yyy, if xxx is already multiline, then we want a penalty
+      // for trying to continue as if this were one line.
+      penalty_current_line +=
+          (CountLines(sub1.String()) - 1) * kPenaltyBrokenLineOnOneLiner;
+    }
+
+    // Break after operator.
+    Printer sub2;
+    InitializeSub(&sub2);
+    sub2.Newline();
+    int penalty_next_line = sub2.Expr(binop->right(), prec_right, at_end);
+    sub2.PrintSuffixComments(root);
+    sub2.FlushComments();
+    penalty_next_line += AssessPenalty(sub2.String());
+
+    // Force a list on the RHS that would normally be a single line into
+    // multiline.
+    bool tried_rhs_multiline = false;
+    Printer sub3;
+    InitializeSub(&sub3);
+    int penalty_multiline_rhs_list = std::numeric_limits<int>::max();
+    const ListNode* rhs_list = binop->right()->AsList();
+    if (is_assignment && rhs_list &&
+        !ListWillBeMultiline(rhs_list->contents(), rhs_list->End())) {
+      sub3.Print(" ");
+      sub3.stack_.push_back(IndentState(start_column, false, false));
+      sub3.Sequence(kSequenceStyleList, rhs_list->contents(), rhs_list->End(),
+                    true);
+      sub3.PrintSuffixComments(root);
+      sub3.FlushComments();
+      sub3.stack_.pop_back();
+      penalty_multiline_rhs_list = AssessPenalty(sub3.String());
+      tried_rhs_multiline = true;
+    }
+
+    // If in all cases it was forced past 80col, then we don't break to avoid
+    // breaking after '=' in the case of:
+    //   variable = "... very long string ..."
+    // as breaking and indenting doesn't make things much more readable, even
+    // though there's fewer characters past the maximum width.
+    bool exceeds_maximum_all_ways =
+        ExceedsMaximumWidth(sub1.String()) &&
+        ExceedsMaximumWidth(sub2.String()) &&
+        (!tried_rhs_multiline || ExceedsMaximumWidth(sub3.String()));
+
+    if (penalty_current_line < penalty_next_line || exceeds_maximum_all_ways) {
+      Print(" ");
+      Expr(binop->right(), prec_right, at_end);
+      at_end = "";
+    } else if (tried_rhs_multiline &&
+               penalty_multiline_rhs_list < penalty_next_line) {
+      // Force a multiline list on the right.
+      Print(" ");
+      stack_.push_back(IndentState(start_column, false, false));
+      Sequence(kSequenceStyleList, rhs_list->contents(), rhs_list->End(), true);
+      stack_.pop_back();
+    } else {
+      // Otherwise, put first argument and op, and indent next.
+      Newline();
+      penalty += std::abs(CurrentColumn() - start_column) *
+                 kPenaltyHorizontalSeparation;
+      Expr(binop->right(), prec_right, at_end);
+      at_end = "";
+    }
+    stack_.pop_back();
+    penalty += (CurrentLine() - start_line) * GetPenaltyForLineBreak();
+  } else if (const BlockNode* block = root->AsBlock()) {
+    Sequence(kSequenceStyleBracedBlock, block->statements(), block->End(),
+             false);
+  } else if (const ConditionNode* condition = root->AsCondition()) {
+    Print("if (");
+    CHECK(at_end.empty());
+    Expr(condition->condition(), kPrecedenceLowest, ") {");
+    Sequence(kSequenceStyleBracedBlockAlreadyOpen,
+             condition->if_true()->statements(), condition->if_true()->End(),
+             false);
+    if (condition->if_false()) {
+      Print(" else ");
+      // If it's a block it's a bare 'else', otherwise it's an 'else if'. See
+      // ConditionNode::Execute.
+      bool is_else_if = condition->if_false()->AsBlock() == nullptr;
+      if (is_else_if) {
+        Expr(condition->if_false(), kPrecedenceLowest, std::string());
+      } else {
+        Sequence(kSequenceStyleBracedBlock,
+                 condition->if_false()->AsBlock()->statements(),
+                 condition->if_false()->AsBlock()->End(), false);
+      }
+    }
+  } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) {
+    penalty += FunctionCall(func_call, at_end);
+    at_end = "";
+  } else if (const IdentifierNode* identifier = root->AsIdentifier()) {
+    Print(identifier->value().value());
+  } else if (const ListNode* list = root->AsList()) {
+    Sequence(kSequenceStyleList, list->contents(), list->End(),
+             /*force_multiline=*/false);
+  } else if (const LiteralNode* literal = root->AsLiteral()) {
+    Print(literal->value().value());
+  } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) {
+    Print(unaryop->op().value());
+    Expr(unaryop->operand(), kPrecedenceUnary, std::string());
+  } else if (const BlockCommentNode* block_comment = root->AsBlockComment()) {
+    Print(block_comment->comment().value());
+  } else if (const EndNode* end = root->AsEnd()) {
+    Print(end->value().value());
+  } else {
+    CHECK(false) << "Unhandled case in Expr.";
+  }
+
+  // Defer any end of line comment until we reach the newline.
+  if (root->comments() && !root->comments()->suffix().empty()) {
+    std::copy(root->comments()->suffix().begin(),
+              root->comments()->suffix().end(), std::back_inserter(comments_));
+  }
+
+  Print(at_end);
+
+  penalty_depth_--;
+  return penalty;
+}
+
+template <class PARSENODE>
+void Printer::Sequence(SequenceStyle style,
+                       const std::vector<std::unique_ptr<PARSENODE>>& list,
+                       const ParseNode* end,
+                       bool force_multiline) {
+  if (style == kSequenceStyleList) {
+    Print("[");
+  } else if (style == kSequenceStyleBracedBlock) {
+    Print("{");
+  } else if (style == kSequenceStyleBracedBlockAlreadyOpen) {
+    style = kSequenceStyleBracedBlock;
+  }
+
+  if (style == kSequenceStyleBracedBlock) {
+    force_multiline = true;
+    SortImports(const_cast<std::vector<std::unique_ptr<PARSENODE>>&>(list));
+  }
+
+  force_multiline |= ListWillBeMultiline(list, end);
+
+  if (list.size() == 0 && !force_multiline) {
+    // No elements, and not forcing newlines, print nothing.
+  } else if (list.size() == 1 && !force_multiline) {
+    Print(" ");
+    Expr(list[0].get(), kPrecedenceLowest, std::string());
+    CHECK(!list[0]->comments() || list[0]->comments()->after().empty());
+    Print(" ");
+  } else {
+    stack_.push_back(IndentState(margin() + kIndentSize,
+                                 style == kSequenceStyleList, false));
+    size_t i = 0;
+    for (const auto& x : list) {
+      Newline();
+      // If:
+      // - we're going to output some comments, and;
+      // - we haven't just started this multiline list, and;
+      // - there isn't already a blank line here;
+      // Then: insert one.
+      if (i != 0 && x->comments() && !x->comments()->before().empty() &&
+          !HaveBlankLine()) {
+        Newline();
+      }
+      bool body_of_list = i < list.size() - 1 || style == kSequenceStyleList;
+      bool want_comma =
+          body_of_list && (style == kSequenceStyleList && !x->AsBlockComment());
+      Expr(x.get(), kPrecedenceLowest, want_comma ? "," : std::string());
+      CHECK(!x->comments() || x->comments()->after().empty());
+      if (body_of_list) {
+        if (i < list.size() - 1 &&
+            ShouldAddBlankLineInBetween(list[i].get(), list[i + 1].get()))
+          Newline();
+      }
+      ++i;
+    }
+
+    // Trailing comments.
+    if (end->comments() && !end->comments()->before().empty()) {
+      if (list.size() >= 2)
+        Newline();
+      for (const auto& c : end->comments()->before()) {
+        Newline();
+        TrimAndPrintToken(c);
+      }
+    }
+
+    stack_.pop_back();
+    Newline();
+  }
+
+  // Defer any end of line comment until we reach the newline.
+  if (end->comments() && !end->comments()->suffix().empty()) {
+    std::copy(end->comments()->suffix().begin(),
+              end->comments()->suffix().end(), std::back_inserter(comments_));
+  }
+
+  if (style == kSequenceStyleList)
+    Print("]");
+  else if (style == kSequenceStyleBracedBlock)
+    Print("}");
+}
+
+int Printer::FunctionCall(const FunctionCallNode* func_call,
+                          const std::string& suffix) {
+  int start_line = CurrentLine();
+  int start_column = CurrentColumn();
+  Print(func_call->function().value());
+  Print("(");
+
+  bool have_block = func_call->block() != nullptr;
+  bool force_multiline = false;
+
+  const auto& list = func_call->args()->contents();
+  const ParseNode* end = func_call->args()->End();
+
+  if (end->comments() && !end->comments()->before().empty())
+    force_multiline = true;
+
+  // If there's before line comments, make sure we have a place to put them.
+  for (const auto& i : list) {
+    if (i->comments() && !i->comments()->before().empty())
+      force_multiline = true;
+  }
+
+  // Calculate the penalties for 3 possible layouts:
+  // 1. all on same line;
+  // 2. starting on same line, broken at each comma but paren aligned;
+  // 3. broken to next line + 4, broken at each comma.
+  std::string terminator = ")";
+  if (have_block)
+    terminator += " {";
+  terminator += suffix;
+
+  // Special case to make function calls of one arg taking a long list of
+  // boolean operators not indent.
+  bool continuation_requires_indent =
+      list.size() != 1 || !list[0]->AsBinaryOp();
+
+  // 1: Same line.
+  Printer sub1;
+  InitializeSub(&sub1);
+  sub1.stack_.push_back(
+      IndentState(CurrentColumn(), continuation_requires_indent, false));
+  int penalty_one_line = 0;
+  for (size_t i = 0; i < list.size(); ++i) {
+    penalty_one_line += sub1.Expr(list[i].get(), kPrecedenceLowest,
+                                  i < list.size() - 1 ? ", " : std::string());
+  }
+  sub1.Print(terminator);
+  penalty_one_line += AssessPenalty(sub1.String());
+  // This extra penalty prevents a short second argument from being squeezed in
+  // after a first argument that went multiline (and instead preferring a
+  // variant below).
+  penalty_one_line +=
+      (CountLines(sub1.String()) - 1) * kPenaltyBrokenLineOnOneLiner;
+
+  // 2: Starting on same line, broken at commas.
+  Printer sub2;
+  InitializeSub(&sub2);
+  sub2.stack_.push_back(
+      IndentState(CurrentColumn(), continuation_requires_indent, false));
+  int penalty_multiline_start_same_line = 0;
+  for (size_t i = 0; i < list.size(); ++i) {
+    penalty_multiline_start_same_line +=
+        sub2.Expr(list[i].get(), kPrecedenceLowest,
+                  i < list.size() - 1 ? "," : std::string());
+    if (i < list.size() - 1) {
+      sub2.Newline();
+    }
+  }
+  sub2.Print(terminator);
+  penalty_multiline_start_same_line += AssessPenalty(sub2.String());
+
+  // 3: Starting on next line, broken at commas.
+  Printer sub3;
+  InitializeSub(&sub3);
+  sub3.stack_.push_back(IndentState(margin() + kIndentSize * 2,
+                                    continuation_requires_indent, false));
+  sub3.Newline();
+  int penalty_multiline_start_next_line = 0;
+  for (size_t i = 0; i < list.size(); ++i) {
+    if (i == 0) {
+      penalty_multiline_start_next_line +=
+          std::abs(sub3.CurrentColumn() - start_column) *
+          kPenaltyHorizontalSeparation;
+    }
+    penalty_multiline_start_next_line +=
+        sub3.Expr(list[i].get(), kPrecedenceLowest,
+                  i < list.size() - 1 ? "," : std::string());
+    if (i < list.size() - 1) {
+      sub3.Newline();
+    }
+  }
+  sub3.Print(terminator);
+  penalty_multiline_start_next_line += AssessPenalty(sub3.String());
+
+  int penalty = penalty_multiline_start_next_line;
+  bool fits_on_current_line = false;
+  if (penalty_one_line < penalty_multiline_start_next_line ||
+      penalty_multiline_start_same_line < penalty_multiline_start_next_line) {
+    fits_on_current_line = true;
+    penalty = penalty_one_line;
+    if (penalty_multiline_start_same_line < penalty_one_line) {
+      penalty = penalty_multiline_start_same_line;
+      force_multiline = true;
+    }
+  } else {
+    force_multiline = true;
+  }
+
+  if (list.size() == 0 && !force_multiline) {
+    // No elements, and not forcing newlines, print nothing.
+  } else {
+    if (penalty_multiline_start_next_line < penalty_multiline_start_same_line) {
+      stack_.push_back(IndentState(margin() + kIndentSize * 2,
+                                   continuation_requires_indent, false));
+      Newline();
+    } else {
+      stack_.push_back(
+          IndentState(CurrentColumn(), continuation_requires_indent, false));
+    }
+
+    for (size_t i = 0; i < list.size(); ++i) {
+      const auto& x = list[i];
+      if (i > 0) {
+        if (fits_on_current_line && !force_multiline)
+          Print(" ");
+        else
+          Newline();
+      }
+      bool want_comma = i < list.size() - 1 && !x->AsBlockComment();
+      Expr(x.get(), kPrecedenceLowest, want_comma ? "," : std::string());
+      CHECK(!x->comments() || x->comments()->after().empty());
+      if (i < list.size() - 1) {
+        if (!want_comma)
+          Newline();
+      }
+    }
+
+    // Trailing comments.
+    if (end->comments() && !end->comments()->before().empty()) {
+      if (!list.empty())
+        Newline();
+      for (const auto& c : end->comments()->before()) {
+        Newline();
+        TrimAndPrintToken(c);
+      }
+      Newline();
+    }
+    stack_.pop_back();
+  }
+
+  // Defer any end of line comment until we reach the newline.
+  if (end->comments() && !end->comments()->suffix().empty()) {
+    std::copy(end->comments()->suffix().begin(),
+              end->comments()->suffix().end(), std::back_inserter(comments_));
+  }
+
+  Print(")");
+  Print(suffix);
+
+  if (have_block) {
+    Print(" ");
+    Sequence(kSequenceStyleBracedBlock, func_call->block()->statements(),
+             func_call->block()->End(), false);
+  }
+  return penalty + (CurrentLine() - start_line) * GetPenaltyForLineBreak();
+}
+
+void Printer::InitializeSub(Printer* sub) {
+  sub->stack_ = stack_;
+  sub->comments_ = comments_;
+  sub->penalty_depth_ = penalty_depth_;
+  sub->Print(std::string(CurrentColumn(), 'x'));
+}
+
+template <class PARSENODE>
+bool Printer::ListWillBeMultiline(
+    const std::vector<std::unique_ptr<PARSENODE>>& list,
+    const ParseNode* end) {
+  if (list.size() > 1)
+    return true;
+
+  if (end && end->comments() && !end->comments()->before().empty())
+    return true;
+
+  // If there's before or suffix line comments, make sure we have a place to put
+  // them.
+  for (const auto& i : list) {
+    if (i->comments() && (!i->comments()->before().empty() ||
+                          !i->comments()->suffix().empty())) {
+      return true;
+    }
+  }
+
+  // When a scope is used as a list entry, it's too complicated to go one a
+  // single line (the block will always be formatted multiline itself).
+  if (list.size() >= 1 && list[0]->AsBlock())
+    return true;
+
+  return false;
+}
+
+void DoFormat(const ParseNode* root,
+              TreeDumpMode dump_tree,
+              std::string* output,
+              std::string* dump_output) {
+  if (dump_tree == TreeDumpMode::kPlainText) {
+    std::ostringstream os;
+    RenderToText(root->GetJSONNode(), 0, os);
+    *dump_output = os.str();
+  } else if (dump_tree == TreeDumpMode::kJSON) {
+    std::string os;
+    base::JSONWriter::WriteWithOptions(
+        root->GetJSONNode(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &os);
+    *dump_output = os;
+  }
+
+  Printer pr;
+  pr.Block(root);
+  *output = pr.String();
+}
+
+}  // namespace
+
+bool FormatJsonToString(const std::string& json, std::string* output) {
+  base::JSONReader reader;
+  std::unique_ptr<base::Value> json_root = reader.Read(json);
+  std::unique_ptr<ParseNode> root = ParseNode::BuildFromJSON(*json_root);
+  DoFormat(root.get(), TreeDumpMode::kInactive, output, nullptr);
+  return true;
+}
+
+bool FormatStringToString(const std::string& input,
+                          TreeDumpMode dump_tree,
+                          std::string* output,
+                          std::string* dump_output) {
+  SourceFile source_file;
+  InputFile file(source_file);
+  file.SetContents(input);
+  Err err;
+  // Tokenize.
+  std::vector<Token> tokens =
+      Tokenizer::Tokenize(&file, &err, WhitespaceTransform::kInvalidToSpace);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return false;
+  }
+
+  // Parse.
+  std::unique_ptr<ParseNode> parse_node = Parser::Parse(tokens, &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return false;
+  }
+
+  DoFormat(parse_node.get(), dump_tree, output, dump_output);
+  return true;
+}
+
+int RunFormat(const std::vector<std::string>& args) {
+#if defined(OS_WIN)
+  // Set to binary mode to prevent converting newlines to \r\n.
+  _setmode(_fileno(stdout), _O_BINARY);
+  _setmode(_fileno(stderr), _O_BINARY);
+#endif
+
+  bool dry_run =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDryRun);
+  TreeDumpMode dump_tree = TreeDumpMode::kInactive;
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree)) {
+    std::string tree_type =
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+            kSwitchDumpTree);
+    if (tree_type == kSwitchTreeTypeJSON) {
+      dump_tree = TreeDumpMode::kJSON;
+    } else if (tree_type.empty() || tree_type == kSwitchTreeTypeText) {
+      dump_tree = TreeDumpMode::kPlainText;
+    } else {
+      Err(Location(), tree_type +
+                          " is an invalid value for --dump-tree. Specify "
+                          "\"" +
+                          kSwitchTreeTypeText + "\" or \"" +
+                          kSwitchTreeTypeJSON + "\".\n")
+          .PrintToStdout();
+      return 1;
+    }
+  }
+  bool from_stdin =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin);
+
+  if (dry_run) {
+    // --dry-run only works with an actual file to compare to.
+    from_stdin = false;
+  }
+
+  bool quiet =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet);
+
+  if (from_stdin) {
+    if (args.size() != 0) {
+      Err(Location(), "Expecting no arguments when reading from stdin.\n")
+          .PrintToStdout();
+      return 1;
+    }
+    std::string input = ReadStdin();
+    std::string output;
+    std::string dump_output;
+    if (!FormatStringToString(input, dump_tree, &output, &dump_output))
+      return 1;
+    printf("%s", dump_output.c_str());
+    printf("%s", output.c_str());
+    return 0;
+  }
+
+  if (args.size() == 0) {
+    Err(Location(), "Expecting one or more arguments, see `gn help format`.\n")
+        .PrintToStdout();
+    return 1;
+  }
+
+  Setup setup;
+  SourceDir source_dir =
+      SourceDirForCurrentDirectory(setup.build_settings().root_path());
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchReadTree)) {
+    std::string tree_type =
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+            kSwitchReadTree);
+    if (tree_type != kSwitchTreeTypeJSON) {
+      Err(Location(), "Only json supported for read-tree.\n").PrintToStdout();
+      return 1;
+    }
+
+    if (args.size() != 1) {
+      Err(Location(),
+          "Expect exactly one .gn when reading tree from json on stdin.\n")
+          .PrintToStdout();
+      return 1;
+    }
+    Err err;
+    SourceFile file =
+        source_dir.ResolveRelativeFile(Value(nullptr, args[0]), &err);
+    if (err.has_error()) {
+      err.PrintToStdout();
+      return 1;
+    }
+    base::FilePath to_format = setup.build_settings().GetFullPath(file);
+    std::string output;
+    FormatJsonToString(ReadStdin(), &output);
+    if (base::WriteFile(to_format, output.data(),
+                        static_cast<int>(output.size())) == -1) {
+      Err(Location(), std::string("Failed to write output to \"") +
+                          FilePathToUTF8(to_format) + std::string("\"."))
+          .PrintToStdout();
+      return 1;
+    }
+    if (!quiet) {
+      printf("Wrote rebuilt from json to '%s'.\n",
+             FilePathToUTF8(to_format).c_str());
+    }
+    return 0;
+  }
+
+  // TODO(scottmg): Eventually, this list of files should be processed in
+  // parallel.
+  int exit_code = 0;
+  for (const auto& arg : args) {
+    Err err;
+    SourceFile file = source_dir.ResolveRelativeFile(Value(nullptr, arg), &err);
+    if (err.has_error()) {
+      err.PrintToStdout();
+      exit_code = 1;
+      continue;
+    }
+
+    base::FilePath to_format = setup.build_settings().GetFullPath(file);
+    std::string original_contents;
+    if (!base::ReadFileToString(to_format, &original_contents)) {
+      Err(Location(),
+          std::string("Couldn't read \"") + FilePathToUTF8(to_format))
+          .PrintToStdout();
+      exit_code = 1;
+      continue;
+    }
+
+    std::string output_string;
+    std::string dump_output_string;
+    if (!FormatStringToString(original_contents, dump_tree, &output_string,
+                              &dump_output_string)) {
+      exit_code = 1;
+      continue;
+    }
+    printf("%s", dump_output_string.c_str());
+    if (dump_tree == TreeDumpMode::kInactive) {
+      if (dry_run) {
+        if (original_contents != output_string) {
+          printf("%s\n", arg.c_str());
+          exit_code = 2;
+        }
+        continue;
+      }
+      // Update the file in-place.
+      if (original_contents != output_string) {
+        if (base::WriteFile(to_format, output_string.data(),
+                            static_cast<int>(output_string.size())) == -1) {
+          Err(Location(),
+              std::string("Failed to write formatted output back to \"") +
+                  FilePathToUTF8(to_format) + std::string("\"."))
+              .PrintToStdout();
+          exit_code = 1;
+          continue;
+        }
+        if (!quiet) {
+          printf("Wrote formatted to '%s'.\n",
+                 FilePathToUTF8(to_format).c_str());
+        }
+      }
+    }
+  }
+
+  return exit_code;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_format.h b/src/gn/command_format.h
new file mode 100644 (file)
index 0000000..0eb667f
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_COMAND_FORMAT_H_
+#define TOOLS_GN_COMAND_FORMAT_H_
+
+#include <string>
+
+class Setup;
+class SourceFile;
+
+namespace commands {
+
+enum class TreeDumpMode {
+  // Normal operation mode. Format the input file.
+  kInactive,
+
+  // Output the token tree with indented plain text. For debugging.
+  kPlainText,
+
+  // Output the token tree in JSON format. Used for exporting a tree to another
+  // program.
+  kJSON
+};
+
+bool FormatJsonToString(const std::string& input, std::string* output);
+
+bool FormatStringToString(const std::string& input,
+                          TreeDumpMode dump_tree,
+                          std::string* output,
+                          std::string* dump_output);
+
+}  // namespace commands
+
+#endif  // TOOLS_GN_COMAND_FORMAT_H_
diff --git a/src/gn/command_format_unittest.cc b/src/gn/command_format_unittest.cc
new file mode 100644 (file)
index 0000000..9112dd3
--- /dev/null
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/command_format.h"
+
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "gn/commands.h"
+#include "gn/setup.h"
+#include "gn/test_with_scheduler.h"
+#include "util/exe_path.h"
+#include "util/test/test.h"
+
+using FormatTest = TestWithScheduler;
+
+#define FORMAT_TEST(n)                                                      \
+  TEST_F(FormatTest, n) {                                                   \
+    ::Setup setup;                                                          \
+    std::string input;                                                      \
+    std::string out;                                                        \
+    std::string expected;                                                   \
+    base::FilePath src_dir =                                                \
+        GetExePath().DirName().Append(FILE_PATH_LITERAL(".."));             \
+    base::SetCurrentDirectory(src_dir);                                     \
+    ASSERT_TRUE(base::ReadFileToString(                                     \
+        base::FilePath(FILE_PATH_LITERAL("src/gn/format_test_data/")        \
+                           FILE_PATH_LITERAL(#n) FILE_PATH_LITERAL(".gn")), \
+        &input));                                                           \
+    ASSERT_TRUE(base::ReadFileToString(                                     \
+        base::FilePath(FILE_PATH_LITERAL("src/gn/format_test_data/")        \
+                           FILE_PATH_LITERAL(#n)                            \
+                               FILE_PATH_LITERAL(".golden")),               \
+        &expected));                                                        \
+    EXPECT_TRUE(commands::FormatStringToString(                             \
+        input, commands::TreeDumpMode::kInactive, &out, nullptr));          \
+    EXPECT_EQ(expected, out);                                               \
+    /* Make sure formatting the output doesn't cause further changes. */    \
+    std::string out_again;                                                  \
+    EXPECT_TRUE(commands::FormatStringToString(                             \
+        out, commands::TreeDumpMode::kInactive, &out_again, nullptr));      \
+    ASSERT_EQ(out, out_again);                                              \
+    /* Make sure we can roundtrip to json without any changes. */           \
+    std::string as_json;                                                    \
+    std::string unused;                                                     \
+    EXPECT_TRUE(commands::FormatStringToString(                             \
+        out_again, commands::TreeDumpMode::kJSON, &unused, &as_json));      \
+    std::string rewritten;                                                  \
+    EXPECT_TRUE(commands::FormatJsonToString(as_json, &rewritten));         \
+    ASSERT_EQ(out, rewritten);                                              \
+  }
+
+// These are expanded out this way rather than a runtime loop so that
+// --gtest_filter works as expected for individual test running.
+FORMAT_TEST(001)
+FORMAT_TEST(002)
+FORMAT_TEST(003)
+FORMAT_TEST(004)
+FORMAT_TEST(005)
+FORMAT_TEST(006)
+FORMAT_TEST(007)
+FORMAT_TEST(008)
+FORMAT_TEST(009)
+FORMAT_TEST(010)
+FORMAT_TEST(011)
+FORMAT_TEST(012)
+FORMAT_TEST(013)
+FORMAT_TEST(014)
+FORMAT_TEST(015)
+FORMAT_TEST(016)
+FORMAT_TEST(017)
+FORMAT_TEST(018)
+FORMAT_TEST(019)
+FORMAT_TEST(020)
+FORMAT_TEST(021)
+FORMAT_TEST(022)
+FORMAT_TEST(023)
+FORMAT_TEST(024)
+FORMAT_TEST(025)
+FORMAT_TEST(026)
+FORMAT_TEST(027)
+FORMAT_TEST(028)
+FORMAT_TEST(029)
+FORMAT_TEST(030)
+FORMAT_TEST(031)
+FORMAT_TEST(032)
+FORMAT_TEST(033)
+// TODO(scottmg): args+rebase_path unnecessarily split: FORMAT_TEST(034)
+FORMAT_TEST(035)
+FORMAT_TEST(036)
+FORMAT_TEST(037)
+FORMAT_TEST(038)
+FORMAT_TEST(039)
+FORMAT_TEST(040)
+FORMAT_TEST(041)
+FORMAT_TEST(042)
+FORMAT_TEST(043)
+FORMAT_TEST(044)
+FORMAT_TEST(045)
+FORMAT_TEST(046)
+FORMAT_TEST(047)
+FORMAT_TEST(048)
+// TODO(scottmg): Eval is broken (!) and comment output might have extra ,
+//                FORMAT_TEST(049)
+FORMAT_TEST(050)
+FORMAT_TEST(051)
+FORMAT_TEST(052)
+FORMAT_TEST(053)
+FORMAT_TEST(054)
+FORMAT_TEST(055)
+FORMAT_TEST(056)
+FORMAT_TEST(057)
+FORMAT_TEST(058)
+FORMAT_TEST(059)
+FORMAT_TEST(060)
+FORMAT_TEST(061)
+FORMAT_TEST(062)
+FORMAT_TEST(063)
+FORMAT_TEST(064)
+FORMAT_TEST(065)
+FORMAT_TEST(066)
+FORMAT_TEST(067)
+FORMAT_TEST(068)
+FORMAT_TEST(069)
+FORMAT_TEST(070)
+FORMAT_TEST(071)
+FORMAT_TEST(072)
+FORMAT_TEST(073)
+FORMAT_TEST(074)
+FORMAT_TEST(075)
+FORMAT_TEST(076)
+FORMAT_TEST(077)
+FORMAT_TEST(078)
+FORMAT_TEST(079)
+FORMAT_TEST(080)
+FORMAT_TEST(081)
+FORMAT_TEST(082)
+FORMAT_TEST(083)
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
new file mode 100644 (file)
index 0000000..e5248b9
--- /dev/null
@@ -0,0 +1,695 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <mutex>
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/timer/elapsed_timer.h"
+#include "gn/build_settings.h"
+#include "gn/commands.h"
+#include "gn/compile_commands_writer.h"
+#include "gn/eclipse_writer.h"
+#include "gn/filesystem_utils.h"
+#include "gn/json_project_writer.h"
+#include "gn/ninja_target_writer.h"
+#include "gn/ninja_tools.h"
+#include "gn/ninja_writer.h"
+#include "gn/qt_creator_writer.h"
+#include "gn/runtime_deps.h"
+#include "gn/rust_project_writer.h"
+#include "gn/scheduler.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/visual_studio_writer.h"
+#include "gn/xcode_writer.h"
+
+namespace commands {
+
+namespace {
+
+const char kSwitchCheck[] = "check";
+const char kSwitchCleanStale[] = "clean-stale";
+const char kSwitchFilters[] = "filters";
+const char kSwitchIde[] = "ide";
+const char kSwitchIdeValueEclipse[] = "eclipse";
+const char kSwitchIdeValueQtCreator[] = "qtcreator";
+const char kSwitchIdeValueVs[] = "vs";
+const char kSwitchIdeValueVs2013[] = "vs2013";
+const char kSwitchIdeValueVs2015[] = "vs2015";
+const char kSwitchIdeValueVs2017[] = "vs2017";
+const char kSwitchIdeValueVs2019[] = "vs2019";
+const char kSwitchIdeValueWinSdk[] = "winsdk";
+const char kSwitchIdeValueXcode[] = "xcode";
+const char kSwitchIdeValueJson[] = "json";
+const char kSwitchIdeRootTarget[] = "ide-root-target";
+const char kSwitchNinjaExecutable[] = "ninja-executable";
+const char kSwitchNinjaExtraArgs[] = "ninja-extra-args";
+const char kSwitchNoDeps[] = "no-deps";
+const char kSwitchSln[] = "sln";
+const char kSwitchXcodeProject[] = "xcode-project";
+const char kSwitchXcodeBuildSystem[] = "xcode-build-system";
+const char kSwitchXcodeBuildsystemValueLegacy[] = "legacy";
+const char kSwitchXcodeBuildsystemValueNew[] = "new";
+const char kSwitchJsonFileName[] = "json-file-name";
+const char kSwitchJsonIdeScript[] = "json-ide-script";
+const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
+const char kSwitchExportCompileCommands[] = "export-compile-commands";
+const char kSwitchExportRustProject[] = "export-rust-project";
+
+// Collects Ninja rules for each toolchain. The lock protectes the rules.
+struct TargetWriteInfo {
+  std::mutex lock;
+  NinjaWriter::PerToolchainRules rules;
+};
+
+// Called on worker thread to write the ninja file.
+void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) {
+  std::string rule = NinjaTargetWriter::RunAndWriteFile(target);
+  DCHECK(!rule.empty());
+
+  {
+    std::lock_guard<std::mutex> lock(write_info->lock);
+    write_info->rules[target->toolchain()].emplace_back(target,
+                                                        std::move(rule));
+  }
+}
+
+// Called on the main thread.
+void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info,
+                                      const BuilderRecord* record) {
+  const Item* item = record->item();
+  const Target* target = item->AsTarget();
+  if (target) {
+    g_scheduler->ScheduleWork(
+        [write_info, target]() { BackgroundDoWrite(write_info, target); });
+  }
+}
+
+// Returns a pointer to the target with the given file as an output, or null
+// if no targets generate the file. This is brute force since this is an
+// error condition and performance shouldn't matter.
+const Target* FindTargetThatGeneratesFile(const Builder& builder,
+                                          const SourceFile& file) {
+  std::vector<const Target*> targets = builder.GetAllResolvedTargets();
+  if (targets.empty())
+    return nullptr;
+
+  OutputFile output_file(targets[0]->settings()->build_settings(), file);
+  for (const Target* target : targets) {
+    for (const auto& cur_output : target->computed_outputs()) {
+      if (cur_output == output_file)
+        return target;
+    }
+  }
+  return nullptr;
+}
+
+// Prints an error that the given file was present as a source or input in
+// the given target(s) but was not generated by any of its dependencies.
+void PrintInvalidGeneratedInput(const Builder& builder,
+                                const SourceFile& file,
+                                const std::vector<const Target*>& targets) {
+  std::string err;
+
+  // Only show the toolchain labels (which can be confusing) if something
+  // isn't the default.
+  bool show_toolchains = false;
+  const Label& default_toolchain =
+      targets[0]->settings()->default_toolchain_label();
+  for (const Target* target : targets) {
+    if (target->settings()->toolchain_label() != default_toolchain) {
+      show_toolchains = true;
+      break;
+    }
+  }
+
+  const Target* generator = FindTargetThatGeneratesFile(builder, file);
+  if (generator &&
+      generator->settings()->toolchain_label() != default_toolchain)
+    show_toolchains = true;
+
+  const std::string target_str = targets.size() > 1 ? "targets" : "target";
+  err += "The file:\n";
+  err += "  " + file.value() + "\n";
+  err += "is listed as an input or source for the " + target_str + ":\n";
+  for (const Target* target : targets)
+    err += "  " + target->label().GetUserVisibleName(show_toolchains) + "\n";
+
+  if (generator) {
+    err += "but this file was not generated by any dependencies of the " +
+           target_str + ". The target\nthat generates the file is:\n  ";
+    err += generator->label().GetUserVisibleName(show_toolchains);
+  } else {
+    err += "but no targets in the build generate that file.";
+  }
+
+  Err(Location(), "Input to " + target_str + " not generated by a dependency.",
+      err)
+      .PrintToStdout();
+}
+
+bool CheckForInvalidGeneratedInputs(Setup* setup) {
+  std::multimap<SourceFile, const Target*> unknown_inputs =
+      g_scheduler->GetUnknownGeneratedInputs();
+  if (unknown_inputs.empty())
+    return true;  // No bad files.
+
+  int errors_found = 0;
+  auto cur = unknown_inputs.begin();
+  while (cur != unknown_inputs.end()) {
+    errors_found++;
+    auto end_of_range = unknown_inputs.upper_bound(cur->first);
+
+    // Package the values more conveniently for printing.
+    SourceFile bad_input = cur->first;
+    std::vector<const Target*> targets;
+    while (cur != end_of_range)
+      targets.push_back((cur++)->second);
+
+    PrintInvalidGeneratedInput(setup->builder(), bad_input, targets);
+    OutputString("\n");
+  }
+
+  OutputString(
+      "If you have generated inputs, there needs to be a dependency path "
+      "between the\ntwo targets in addition to just listing the files. For "
+      "indirect dependencies,\nthe intermediate ones must be public_deps. "
+      "data_deps don't count since they're\nonly runtime dependencies. If "
+      "you think a dependency chain exists, it might be\nbecause the chain "
+      "is private. Try \"gn path\" to analyze.\n");
+
+  if (errors_found > 1) {
+    OutputString(base::StringPrintf("\n%d generated input errors found.\n",
+                                    errors_found),
+                 DECORATION_YELLOW);
+  }
+  return false;
+}
+
+bool RunIdeWriter(const std::string& ide,
+                  const BuildSettings* build_settings,
+                  const Builder& builder,
+                  Err* err) {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  bool quiet = command_line->HasSwitch(switches::kQuiet);
+  base::ElapsedTimer timer;
+
+  if (ide == kSwitchIdeValueEclipse) {
+    bool res = EclipseWriter::RunAndWriteFile(build_settings, builder, err);
+    if (res && !quiet) {
+      OutputString("Generating Eclipse settings took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  } else if (ide == kSwitchIdeValueVs || ide == kSwitchIdeValueVs2013 ||
+             ide == kSwitchIdeValueVs2015 || ide == kSwitchIdeValueVs2017 ||
+             ide == kSwitchIdeValueVs2019) {
+    VisualStudioWriter::Version version = VisualStudioWriter::Version::Vs2019;
+    if (ide == kSwitchIdeValueVs2013)
+      version = VisualStudioWriter::Version::Vs2013;
+    else if (ide == kSwitchIdeValueVs2015)
+      version = VisualStudioWriter::Version::Vs2015;
+    else if (ide == kSwitchIdeValueVs2017)
+      version = VisualStudioWriter::Version::Vs2017;
+
+    std::string sln_name;
+    if (command_line->HasSwitch(kSwitchSln))
+      sln_name = command_line->GetSwitchValueASCII(kSwitchSln);
+    std::string filters;
+    if (command_line->HasSwitch(kSwitchFilters))
+      filters = command_line->GetSwitchValueASCII(kSwitchFilters);
+    std::string win_kit;
+    if (command_line->HasSwitch(kSwitchIdeValueWinSdk))
+      win_kit = command_line->GetSwitchValueASCII(kSwitchIdeValueWinSdk);
+    std::string ninja_extra_args;
+    if (command_line->HasSwitch(kSwitchNinjaExtraArgs))
+      ninja_extra_args =
+          command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs);
+    bool no_deps = command_line->HasSwitch(kSwitchNoDeps);
+    bool res = VisualStudioWriter::RunAndWriteFiles(
+        build_settings, builder, version, sln_name, filters, win_kit,
+        ninja_extra_args, no_deps, err);
+    if (res && !quiet) {
+      OutputString("Generating Visual Studio projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  } else if (ide == kSwitchIdeValueXcode) {
+    XcodeWriter::Options options = {
+        command_line->GetSwitchValueASCII(kSwitchXcodeProject),
+        command_line->GetSwitchValueASCII(kSwitchIdeRootTarget),
+        command_line->GetSwitchValueASCII(kSwitchNinjaExecutable),
+        command_line->GetSwitchValueASCII(kSwitchFilters),
+        XcodeBuildSystem::kLegacy,
+    };
+
+    if (options.project_name.empty()) {
+      options.project_name = "all";
+    }
+
+    const std::string build_system =
+        command_line->GetSwitchValueASCII(kSwitchXcodeBuildSystem);
+    if (!build_system.empty()) {
+      if (build_system == kSwitchXcodeBuildsystemValueNew) {
+        options.build_system = XcodeBuildSystem::kNew;
+      } else if (build_system == kSwitchXcodeBuildsystemValueLegacy) {
+        options.build_system = XcodeBuildSystem::kLegacy;
+      } else {
+        *err = Err(Location(), "Unknown build system: " + build_system);
+        return false;
+      }
+    }
+
+    bool res =
+        XcodeWriter::RunAndWriteFiles(build_settings, builder, options, err);
+    if (res && !quiet) {
+      OutputString("Generating Xcode projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  } else if (ide == kSwitchIdeValueQtCreator) {
+    std::string root_target;
+    if (command_line->HasSwitch(kSwitchIdeRootTarget))
+      root_target = command_line->GetSwitchValueASCII(kSwitchIdeRootTarget);
+    bool res = QtCreatorWriter::RunAndWriteFile(build_settings, builder, err,
+                                                root_target);
+    if (res && !quiet) {
+      OutputString("Generating QtCreator projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  } else if (ide == kSwitchIdeValueJson) {
+    std::string file_name =
+        command_line->GetSwitchValueASCII(kSwitchJsonFileName);
+    if (file_name.empty())
+      file_name = "project.json";
+    std::string exec_script =
+        command_line->GetSwitchValueASCII(kSwitchJsonIdeScript);
+    std::string exec_script_extra_args =
+        command_line->GetSwitchValueASCII(kSwitchJsonIdeScriptArgs);
+    std::string filters = command_line->GetSwitchValueASCII(kSwitchFilters);
+
+    bool res = JSONProjectWriter::RunAndWriteFiles(
+        build_settings, builder, file_name, exec_script, exec_script_extra_args,
+        filters, quiet, err);
+    if (res && !quiet) {
+      OutputString("Generating JSON projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  }
+
+  *err = Err(Location(), "Unknown IDE: " + ide);
+  return false;
+}
+
+bool RunRustProjectWriter(const BuildSettings* build_settings,
+                          const Builder& builder,
+                          Err* err) {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  bool quiet = command_line->HasSwitch(switches::kQuiet);
+  base::ElapsedTimer timer;
+
+  std::string file_name = "rust-project.json";
+  bool res = RustProjectWriter::RunAndWriteFiles(build_settings, builder,
+                                                 file_name, quiet, err);
+  if (res && !quiet) {
+    OutputString("Generating rust-project.json took " +
+                 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                 "ms\n");
+  }
+  return res;
+}
+
+bool RunCompileCommandsWriter(const BuildSettings* build_settings,
+                              const Builder& builder,
+                              Err* err) {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  bool quiet = command_line->HasSwitch(switches::kQuiet);
+  base::ElapsedTimer timer;
+
+  std::string file_name = "compile_commands.json";
+  std::string target_filters =
+      command_line->GetSwitchValueASCII(kSwitchExportCompileCommands);
+
+  bool res = CompileCommandsWriter::RunAndWriteFiles(
+      build_settings, builder, file_name, target_filters, quiet, err);
+  if (res && !quiet) {
+    OutputString("Generating compile_commands took " +
+                 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                 "ms\n");
+  }
+  return res;
+}
+
+bool RunNinjaPostProcessTools(const BuildSettings* build_settings,
+                              base::FilePath ninja_executable,
+                              bool is_regeneration,
+                              bool clean_stale,
+                              Err* err) {
+  // If the user did not specify an executable, skip running the post processing
+  // tools. Since these tools can re-write ninja build log and dep logs, it is
+  // really important that ninja executable used for tools matches the
+  // executable that is used for builds.
+  if (ninja_executable.empty()) {
+    if (clean_stale) {
+      *err = Err(Location(), "No --ninja-executable provided.",
+                 "--clean-stale requires a ninja executable to run. You can "
+                 "provide one on the command line via --ninja-executable.");
+      return false;
+    }
+
+    return true;
+  }
+
+  base::FilePath build_dir =
+      build_settings->GetFullPath(build_settings->build_dir());
+
+  if (clean_stale) {
+    if (build_settings->ninja_required_version() < Version{1, 10, 0}) {
+      *err = Err(Location(), "Need a ninja executable at least version 1.10.0.",
+                 "--clean-stale requires a ninja executable of version 1.10.0 "
+                 "or later.");
+      return false;
+    }
+
+    if (!InvokeNinjaCleanDeadTool(ninja_executable, build_dir, err)) {
+      return false;
+    }
+
+    if(!InvokeNinjaRecompactTool(ninja_executable, build_dir, err)) {
+      return false;
+    }
+  }
+
+  // If we have a ninja version that supports restat, we should restat the
+  // build.ninja file so the next ninja invocation will use the right mtime. If
+  // gen is being invoked as part of a re-gen (ie, ninja is invoking gn gen),
+  // then we can elide this restat, as ninja will restat build.ninja anyways
+  // after it is complete.
+  if (!is_regeneration &&
+      build_settings->ninja_required_version() >= Version{1, 10, 0}) {
+    std::vector<base::FilePath> files_to_restat{
+        base::FilePath(FILE_PATH_LITERAL("build.ninja"))};
+    if (!InvokeNinjaRestatTool(ninja_executable, build_dir, files_to_restat,
+                               err)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+const char kGen[] = "gen";
+const char kGen_HelpShort[] = "gen: Generate ninja files.";
+const char kGen_Help[] =
+    R"(gn gen [--check] [<ide options>] <out_dir>
+
+  Generates ninja files from the current tree and puts them in the given output
+  directory.
+
+  The output directory can be a source-repo-absolute path name such as:
+      //out/foo
+  Or it can be a directory relative to the current directory such as:
+      out/foo
+
+  "gn gen --check" is the same as running "gn check". "gn gen --check=system" is
+  the same as running "gn check --check-system".  See "gn help check" for
+  documentation on that mode.
+
+  See "gn help switches" for the common command-line switches.
+
+General options
+
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use. This executable will
+      be used as an IDE option to indicate which ninja to use for building. This
+      executable will also be used as part of the gen process for triggering a
+      restat on generated ninja files and for use with --clean-stale.
+
+  --clean-stale
+      This option will cause no longer needed output files to be removed from
+      the build directory, and their records pruned from the ninja build log and
+      dependency database after the ninja build graph has been generated. This
+      option requires a ninja executable of at least version 1.10.0. It can be
+      provided by the --ninja-executable switch. Also see "gn help clean_stale".
+
+IDE options
+
+  GN optionally generates files for IDE. Files won't be overwritten if their
+  contents don't change. Possibilities for <ide options>
+
+  --ide=<ide_name>
+      Generate files for an IDE. Currently supported values:
+      "eclipse" - Eclipse CDT settings file.
+      "vs" - Visual Studio project/solution files.
+             (default Visual Studio version: 2019)
+      "vs2013" - Visual Studio 2013 project/solution files.
+      "vs2015" - Visual Studio 2015 project/solution files.
+      "vs2017" - Visual Studio 2017 project/solution files.
+      "vs2019" - Visual Studio 2019 project/solution files.
+      "xcode" - Xcode workspace/solution files.
+      "qtcreator" - QtCreator project files.
+      "json" - JSON file containing target information
+
+  --filters=<path_prefixes>
+      Semicolon-separated list of label patterns used to limit the set of
+      generated projects (see "gn help label_pattern"). Only matching targets
+      and their dependencies will be included in the solution. Only used for
+      Visual Studio, Xcode and JSON.
+
+Visual Studio Flags
+
+  --sln=<file_name>
+      Override default sln file name ("all"). Solution file is written to the
+      root build directory.
+
+  --no-deps
+      Don't include targets dependencies to the solution. Changes the way how
+      --filters option works. Only directly matching targets are included.
+
+  --winsdk=<sdk_version>
+      Use the specified Windows 10 SDK version to generate project files.
+      As an example, "10.0.15063.0" can be specified to use Creators Update SDK
+      instead of the default one.
+
+  --ninja-extra-args=<string>
+      This string is passed without any quoting to the ninja invocation
+      command-line. Can be used to configure ninja flags, like "-j".
+
+Xcode Flags
+
+  --xcode-project=<file_name>
+      Override default Xcode project file name ("all"). The project file is
+      written to the root build directory.
+
+  --xcode-build-system=<value>
+      Configure the build system to use for the Xcode project. Supported
+      values are (default to "legacy"):
+      "legacy" - Legacy Build system
+      "new" - New Build System
+
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use when building.
+
+  --ninja-extra-args=<string>
+      This string is passed without any quoting to the ninja invocation
+      command-line. Can be used to configure ninja flags, like "-j".
+
+  --ide-root-target=<target_name>
+      Name of the target corresponding to "All" target in Xcode. If unset,
+      "All" invokes ninja without any target and builds everything.
+
+QtCreator Flags
+
+  --ide-root-target=<target_name>
+      Name of the root target for which the QtCreator project will be generated
+      to contain files of it and its dependencies. If unset, the whole build
+      graph will be emitted.
+
+
+Eclipse IDE Support
+
+  GN DOES NOT generate Eclipse CDT projects. Instead, it generates a settings
+  file which can be imported into an Eclipse CDT project. The XML file contains
+  a list of include paths and defines. Because GN does not generate a full
+  .cproject definition, it is not possible to properly define includes/defines
+  for each file individually. Instead, one set of includes/defines is generated
+  for the entire project. This works fairly well but may still result in a few
+  indexer issues here and there.
+
+Generic JSON Output
+
+  Dumps target information to a JSON file and optionally invokes a
+  python script on the generated file. See the comments at the beginning
+  of json_project_writer.cc and desc_builder.cc for an overview of the JSON
+  file format.
+
+  --json-file-name=<json_file_name>
+      Overrides default file name (project.json) of generated JSON file.
+
+  --json-ide-script=<path_to_python_script>
+      Executes python script after the JSON file is generated or updated with
+      new content. Path can be project absolute (//), system absolute (/) or
+      relative, in which case the output directory will be base. Path to
+      generated JSON file will be first argument when invoking script.
+
+  --json-ide-script-args=<argument>
+      Optional second argument that will passed to executed script.
+
+Compilation Database
+
+  --export-rust-project
+      Produces a rust-project.json file in the root of the build directory
+      This is used for various tools in the Rust ecosystem allowing for the
+      replay of individual compilations independent of the build system.
+      This is an unstable format and likely to change without warning.
+
+  --export-compile-commands[=<target_name1,target_name2...>]
+      Produces a compile_commands.json file in the root of the build directory
+      containing an array of “command objects”, where each command object
+      specifies one way a translation unit is compiled in the project. If a list
+      of target_name is supplied, only targets that are reachable from any
+      target in any build file whose name is target_name will be used for
+      “command objects” generation, otherwise all available targets will be used.
+      This is used for various Clang-based tooling, allowing for the replay of
+      individual compilations independent of the build system.
+      e.g. "foo" will match:
+      - "//path/to/src:foo"
+      - "//other/path:foo"
+      - "//foo:foo"
+      and not match:
+      - "//foo:bar"
+)";
+
+int RunGen(const std::vector<std::string>& args) {
+  base::ElapsedTimer timer;
+
+  if (args.size() != 1) {
+    Err(Location(), "Need exactly one build directory to generate.",
+        "I expected something more like \"gn gen out/foo\"\n"
+        "You can also see \"gn help gen\".")
+        .PrintToStdout();
+    return 1;
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup();
+  // Generate an empty args.gn file if it does not exists
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kArgs)) {
+    setup->set_gen_empty_args(true);
+  }
+  if (!setup->DoSetup(args[0], true))
+    return 1;
+
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(kSwitchCheck)) {
+    setup->set_check_public_headers(true);
+    if (command_line->GetSwitchValueASCII(kSwitchCheck) == "system")
+      setup->set_check_system_includes(true);
+  }
+
+  // Cause the load to also generate the ninja files for each target.
+  TargetWriteInfo write_info;
+  setup->builder().set_resolved_and_generated_callback(
+      [&write_info](const BuilderRecord* record) {
+        ItemResolvedAndGeneratedCallback(&write_info, record);
+      });
+
+  // Do the actual load. This will also write out the target ninja files.
+  if (!setup->Run())
+    return 1;
+
+  // Sort the targets in each toolchain according to their label. This makes
+  // the ninja files have deterministic content.
+  for (auto& cur_toolchain : write_info.rules) {
+    std::sort(cur_toolchain.second.begin(), cur_toolchain.second.end(),
+              [](const NinjaWriter::TargetRulePair& a,
+                 const NinjaWriter::TargetRulePair& b) {
+                return a.first->label() < b.first->label();
+              });
+  }
+
+  Err err;
+  // Write the root ninja files.
+  if (!NinjaWriter::RunAndWriteFiles(&setup->build_settings(), setup->builder(),
+                                     write_info.rules, &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (!RunNinjaPostProcessTools(
+          &setup->build_settings(),
+          command_line->GetSwitchValuePath(switches::kNinjaExecutable),
+          command_line->HasSwitch(switches::kRegeneration),
+          command_line->HasSwitch(kSwitchCleanStale), &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (!WriteRuntimeDepsFilesIfNecessary(&setup->build_settings(),
+                                        setup->builder(), &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (!CheckForInvalidGeneratedInputs(setup))
+    return 1;
+
+  if (command_line->HasSwitch(kSwitchIde) &&
+      !RunIdeWriter(command_line->GetSwitchValueASCII(kSwitchIde),
+                    &setup->build_settings(), setup->builder(), &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (command_line->HasSwitch(kSwitchExportCompileCommands) &&
+      !RunCompileCommandsWriter(&setup->build_settings(), setup->builder(),
+                                &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  if (command_line->HasSwitch(kSwitchExportRustProject) &&
+      !RunRustProjectWriter(&setup->build_settings(), setup->builder(), &err)) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  TickDelta elapsed_time = timer.Elapsed();
+
+  if (!command_line->HasSwitch(switches::kQuiet)) {
+    OutputString("Done. ", DECORATION_GREEN);
+
+    size_t targets_collected = 0;
+    for (const auto& rules : write_info.rules)
+      targets_collected += rules.second.size();
+
+    std::string stats =
+        "Made " + base::NumberToString(targets_collected) + " targets from " +
+        base::IntToString(
+            setup->scheduler().input_file_manager()->GetInputFileCount()) +
+        " files in " + base::Int64ToString(elapsed_time.InMilliseconds()) +
+        "ms\n";
+    OutputString(stats);
+  }
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_help.cc b/src/gn/command_help.cc
new file mode 100644 (file)
index 0000000..f03a4e7
--- /dev/null
@@ -0,0 +1,376 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "gn/args.h"
+#include "gn/commands.h"
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/input_conversion.h"
+#include "gn/label.h"
+#include "gn/label_pattern.h"
+#include "gn/metadata.h"
+#include "gn/ninja_build_writer.h"
+#include "gn/output_conversion.h"
+#include "gn/parser.h"
+#include "gn/pattern.h"
+#include "gn/runtime_deps.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_writer.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+
+namespace commands {
+
+namespace {
+
+// Some names exist in multiple sections, these prefixes are used for the
+// internal links to disambiguate when writing markdown.
+const char kCommandLinkPrefix[] = "cmd_";
+const char kFunctionLinkPrefix[] = "func_";
+const char kVariableLinkPrefix[] = "var_";
+
+void PrintToplevelHelp() {
+  PrintSectionHelp("Commands", "<command>", "commands");
+  for (const auto& cmd : commands::GetCommands())
+    PrintShortHelp(cmd.second.help_short, kCommandLinkPrefix + cmd.first);
+
+  // Target declarations.
+  PrintSectionHelp("Target declarations", "<function>", "targets");
+  for (const auto& func : functions::GetFunctions()) {
+    if (func.second.is_target)
+      PrintShortHelp(func.second.help_short, kFunctionLinkPrefix + func.first);
+  }
+
+  // Functions.
+  PrintSectionHelp("Buildfile functions", "<function>", "functions");
+  for (const auto& func : functions::GetFunctions()) {
+    if (!func.second.is_target)
+      PrintShortHelp(func.second.help_short, kFunctionLinkPrefix + func.first);
+  }
+
+  // Built-in variables.
+  PrintSectionHelp("Built-in predefined variables", "<variable>",
+                   "predefined_variables");
+  for (const auto& builtin : variables::GetBuiltinVariables()) {
+    PrintShortHelp(builtin.second.help_short,
+                   kVariableLinkPrefix + builtin.first);
+  }
+
+  // Target variables.
+  PrintSectionHelp("Variables you set in targets", "<variable>",
+                   "target_variables");
+  for (const auto& target : variables::GetTargetVariables()) {
+    PrintShortHelp(target.second.help_short,
+                   kVariableLinkPrefix + target.first);
+  }
+
+  PrintSectionHelp("Other help topics", "", "other");
+  PrintShortHelp("all: Print all the help at once");
+  PrintShortHelp("buildargs: How build arguments work.", "buildargs");
+  PrintShortHelp("dotfile: Info about the toplevel .gn file.", "dotfile");
+  PrintShortHelp("execution: Build graph and execution overview.", "execution");
+  PrintShortHelp("grammar: Language and grammar for GN build files.",
+                 "grammar");
+  PrintShortHelp(
+      "input_conversion: Processing input from exec_script and read_file.",
+      "io_conversion");
+  PrintShortHelp("file_pattern: Matching more than one file.", "file_pattern");
+  PrintShortHelp("label_pattern: Matching more than one label.",
+                 "label_pattern");
+  PrintShortHelp("labels: About labels.", "labels");
+  PrintShortHelp("metadata_collection: About metadata and its collection.",
+                 "metadata_collection");
+  PrintShortHelp("ninja_rules: How Ninja build rules are named.",
+                 "ninja_rules");
+  PrintShortHelp("nogncheck: Annotating includes for checking.", "nogncheck");
+  PrintShortHelp(
+      "output_conversion: Specifies how to transform a value to output.",
+      "io_conversion");
+  PrintShortHelp("runtime_deps: How runtime dependency computation works.",
+                 "runtime_deps");
+  PrintShortHelp("source_expansion: Map sources to outputs for scripts.",
+                 "source_expansion");
+  PrintShortHelp("switches: Show available command-line switches.",
+                 "switch_list");
+}
+
+void PrintSwitchHelp() {
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool is_markdown = cmdline->HasSwitch(switches::kMarkdown);
+
+  // This uses "switch_list" for the tag because Markdown seems to generate
+  // implicit tags for headings that match the strings, and some headings are
+  // labeled "switches".
+  PrintLongHelp(R"(Available global switches
+
+  Do "gn help --the_switch_you_want_help_on" for more. Individual commands may
+  take command-specific switches not listed here. See the help on your specific
+  command for more.
+)",
+                "switch_list");
+
+  if (is_markdown)
+    OutputString("```\n", DECORATION_NONE);
+
+  for (const auto& s : switches::GetSwitches())
+    PrintShortHelp(s.second.short_help);
+
+  if (is_markdown)
+    OutputString("```\n", DECORATION_NONE);
+
+  OutputString("\n");
+}
+
+void PrintAllHelp() {
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool is_markdown = cmdline->HasSwitch(switches::kMarkdown);
+
+  if (is_markdown) {
+    OutputString("# GN Reference\n\n");
+    OutputString(
+        "*This page is automatically generated from* "
+        "`gn help --markdown all`.\n\n");
+
+    // Generate our own table of contents so that we have more control
+    // over what's in and out.
+    OutputString("## Contents\n\n");
+  }
+
+  PrintToplevelHelp();
+
+  OutputString("\n");
+
+  if (is_markdown) {
+    OutputString("## <a name=\"commands\"></a>Commands\n\n", DECORATION_NONE,
+                 NO_ESCAPING);
+  }
+  for (const auto& c : commands::GetCommands())
+    PrintLongHelp(c.second.help, kCommandLinkPrefix + c.first);
+
+  if (is_markdown) {
+    OutputString("## <a name=\"targets\"></a>Target declarations\n\n",
+                 DECORATION_NONE, NO_ESCAPING);
+  }
+  for (const auto& f : functions::GetFunctions()) {
+    if (f.second.is_target)
+      PrintLongHelp(f.second.help, kFunctionLinkPrefix + f.first);
+  }
+
+  if (is_markdown) {
+    OutputString("## <a name=\"functions\"></a>Buildfile functions\n\n",
+                 DECORATION_NONE, NO_ESCAPING);
+  }
+  for (const auto& f : functions::GetFunctions()) {
+    if (!f.second.is_target)
+      PrintLongHelp(f.second.help, kFunctionLinkPrefix + f.first);
+  }
+
+  if (is_markdown) {
+    OutputString(
+        "## <a name=\"predefined_variables\"></a>"
+        "Built-in predefined variables\n\n",
+        DECORATION_NONE, NO_ESCAPING);
+  }
+  for (const auto& v : variables::GetBuiltinVariables())
+    PrintLongHelp(v.second.help, kVariableLinkPrefix + v.first);
+
+  if (is_markdown) {
+    OutputString(
+        "## <a name=\"target_variables\"></a>"
+        "Variables you set in targets\n\n",
+        DECORATION_NONE, NO_ESCAPING);
+  }
+  for (const auto& v : variables::GetTargetVariables())
+    PrintLongHelp(v.second.help, kVariableLinkPrefix + v.first);
+
+  if (is_markdown) {
+    OutputString("## <a name=\"other\"></a>Other help topics\n\n",
+                 DECORATION_NONE, NO_ESCAPING);
+  }
+  PrintLongHelp(kBuildArgs_Help, "buildargs");
+  PrintLongHelp(kDotfile_Help, "dotfile");
+  PrintLongHelp(kExecution_Help, "execution");
+  PrintLongHelp(kGrammar_Help, "grammar");
+  PrintLongHelp(kInputOutputConversion_Help, "io_conversion");
+  PrintLongHelp(kFilePattern_Help, "file_pattern");
+  PrintLongHelp(kLabelPattern_Help, "label_pattern");
+  PrintLongHelp(kLabels_Help, "labels");
+  PrintLongHelp(kMetadata_Help, "metadata_collection");
+  PrintLongHelp(kNinjaRules_Help, "ninja_rules");
+  PrintLongHelp(kNoGnCheck_Help, "nogncheck");
+  PrintLongHelp(kRuntimeDeps_Help, "runtime_deps");
+  PrintLongHelp(kSourceExpansion_Help, "source_expansion");
+
+  PrintSwitchHelp();
+}
+
+// Prints help on the given switch. There should be no leading hyphens. Returns
+// true if the switch was found and help was printed. False means the switch is
+// unknown.
+bool PrintHelpOnSwitch(const std::string& what) {
+  const switches::SwitchInfoMap& all = switches::GetSwitches();
+  switches::SwitchInfoMap::const_iterator found =
+      all.find(std::string_view(what));
+  if (found == all.end())
+    return false;
+  PrintLongHelp(found->second.long_help);
+  return true;
+}
+
+// Special-case help for ambiguous "args" case.
+void PrintArgsHelp() {
+  PrintLongHelp(
+      "The string \"args\" is both a command and a variable for action "
+      "targets.\nShowing help for both...\n\n");
+  PrintLongHelp(commands::kArgs_Help);
+  PrintLongHelp(
+      "\n----------------------------------------------------------------------"
+      "---------\n\n");
+  PrintLongHelp(variables::kArgs_Help);
+}
+
+}  // namespace
+
+const char kHelp[] = "help";
+const char kHelp_HelpShort[] = "help: Does what you think.";
+const char kHelp_Help[] =
+    R"(gn help <anything>
+
+  Yo dawg, I heard you like help on your help so I put help on the help in the
+  help.
+
+  You can also use "all" as the parameter to get all help at once.
+
+Switches
+
+  --markdown
+      Format output in markdown syntax.
+
+Example
+
+  gn help --markdown all
+      Dump all help to stdout in markdown format.
+)";
+
+int RunHelp(const std::vector<std::string>& args) {
+  std::string what;
+  if (args.size() == 0) {
+    // If no argument is specified, check for switches to allow things like
+    // "gn help --args" for help on the args switch.
+    const base::CommandLine::SwitchMap& switches =
+        base::CommandLine::ForCurrentProcess()->GetSwitches();
+    if (switches.empty()) {
+      // Still nothing, show help overview.
+      PrintToplevelHelp();
+      return 0;
+    }
+
+    // Switch help needs to be done separately. The CommandLine will strip the
+    // switch separators so --args will come out as "args" which is then
+    // ambiguous with the variable named "args".
+    if (!PrintHelpOnSwitch(switches.begin()->first))
+      PrintToplevelHelp();
+    return 0;
+  } else {
+    what = args[0];
+  }
+
+  std::vector<std::string_view> all_help_topics;
+
+  // Special-case ambiguous topics.
+  if (what == "args") {
+    PrintArgsHelp();
+    return 0;
+  }
+
+  // Check commands.
+  const commands::CommandInfoMap& command_map = commands::GetCommands();
+  auto found_command = command_map.find(what);
+  if (found_command != command_map.end()) {
+    PrintLongHelp(found_command->second.help);
+    return 0;
+  }
+  for (const auto& entry : command_map)
+    all_help_topics.push_back(entry.first);
+
+  // Check functions.
+  const functions::FunctionInfoMap& function_map = functions::GetFunctions();
+  auto found_function = function_map.find(what);
+  if (found_function != function_map.end())
+    PrintLongHelp(found_function->second.help);
+  for (const auto& entry : function_map)
+    all_help_topics.push_back(entry.first);
+
+  // Builtin variables.
+  const variables::VariableInfoMap& builtin_vars =
+      variables::GetBuiltinVariables();
+  auto found_builtin_var = builtin_vars.find(what);
+  if (found_builtin_var != builtin_vars.end())
+    PrintLongHelp(found_builtin_var->second.help);
+  for (const auto& entry : builtin_vars)
+    all_help_topics.push_back(entry.first);
+
+  // Target variables.
+  const variables::VariableInfoMap& target_vars =
+      variables::GetTargetVariables();
+  auto found_target_var = target_vars.find(what);
+  if (found_target_var != target_vars.end())
+    PrintLongHelp(found_target_var->second.help);
+  for (const auto& entry : target_vars)
+    all_help_topics.push_back(entry.first);
+
+  if (found_function != function_map.end() ||
+      found_builtin_var != builtin_vars.end() ||
+      found_target_var != target_vars.end())
+    return 0;
+
+  // Random other topics.
+  std::map<std::string, void (*)()> random_topics;
+  random_topics["all"] = PrintAllHelp;
+  random_topics["execution"] = []() { PrintLongHelp(kExecution_Help); };
+  random_topics["buildargs"] = []() { PrintLongHelp(kBuildArgs_Help); };
+  random_topics["dotfile"] = []() { PrintLongHelp(kDotfile_Help); };
+  random_topics["grammar"] = []() { PrintLongHelp(kGrammar_Help); };
+  random_topics["io_conversion"] = []() {
+    PrintLongHelp(kInputOutputConversion_Help);
+  };
+  random_topics["file_pattern"] = []() { PrintLongHelp(kFilePattern_Help); };
+  random_topics["label_pattern"] = []() { PrintLongHelp(kLabelPattern_Help); };
+  random_topics["labels"] = []() { PrintLongHelp(kLabels_Help); };
+  random_topics["metadata_collection"] = []() {
+    PrintLongHelp(kMetadata_Help);
+  };
+  random_topics["ninja_rules"] = []() { PrintLongHelp(kNinjaRules_Help); };
+  random_topics["nogncheck"] = []() { PrintLongHelp(kNoGnCheck_Help); };
+  random_topics["runtime_deps"] = []() { PrintLongHelp(kRuntimeDeps_Help); };
+  random_topics["source_expansion"] = []() {
+    PrintLongHelp(kSourceExpansion_Help);
+  };
+  random_topics["switches"] = PrintSwitchHelp;
+  auto found_random_topic = random_topics.find(what);
+  if (found_random_topic != random_topics.end()) {
+    found_random_topic->second();
+    return 0;
+  }
+  for (const auto& entry : random_topics)
+    all_help_topics.push_back(entry.first);
+
+  // No help on this.
+  Err(Location(), "No help on \"" + what + "\".").PrintToStdout();
+  std::string_view suggestion = SpellcheckString(what, all_help_topics);
+  if (suggestion.empty()) {
+    OutputString("Run `gn help` for a list of available topics.\n",
+                 DECORATION_NONE);
+  } else {
+    OutputString("Did you mean `gn help " + std::string(suggestion) + "`?\n",
+                 DECORATION_NONE);
+  }
+  return 1;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_ls.cc b/src/gn/command_ls.cc
new file mode 100644 (file)
index 0000000..0dbf911
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <set>
+
+#include "base/command_line.h"
+#include "gn/commands.h"
+#include "gn/label_pattern.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+
+namespace commands {
+
+const char kLs[] = "ls";
+const char kLs_HelpShort[] = "ls: List matching targets.";
+const char kLs_Help[] =
+    R"(gn ls <out_dir> [<label_pattern>] [--default-toolchain] [--as=...]
+      [--type=...] [--testonly=...]
+
+  Lists all targets matching the given pattern for the given build directory.
+  By default, only targets in the default toolchain will be matched unless a
+  toolchain is explicitly supplied.
+
+  If the label pattern is unspecified, list all targets. The label pattern is
+  not a general regular expression (see "gn help label_pattern"). If you need
+  more complex expressions, pipe the result through grep.
+
+Options
+
+)" TARGET_PRINTING_MODE_COMMAND_LINE_HELP "\n" DEFAULT_TOOLCHAIN_SWITCH_HELP
+    "\n" TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
+    "\n" TARGET_TYPE_FILTER_COMMAND_LINE_HELP
+    R"(
+Examples
+
+  gn ls out/Debug
+      Lists all targets in the default toolchain.
+
+  gn ls out/Debug "//base/*"
+      Lists all targets in the directory base and all subdirectories.
+
+  gn ls out/Debug "//base:*"
+      Lists all targets defined in //base/BUILD.gn.
+
+  gn ls out/Debug //base --as=output
+      Lists the build output file for //base:base
+
+  gn ls out/Debug --type=executable
+      Lists all executables produced by the build.
+
+  gn ls out/Debug "//base/*" --as=output | xargs ninja -C out/Debug
+      Builds all targets in //base and all subdirectories.
+)";
+
+int RunLs(const std::vector<std::string>& args) {
+  if (args.size() == 0) {
+    Err(Location(), "Unknown command format. See \"gn help ls\"",
+        "Usage: \"gn ls <build dir> [<label_pattern>]*\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false) || !setup->Run())
+    return 1;
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain);
+
+  std::vector<const Target*> matches;
+  if (args.size() > 1) {
+    // Some patterns or explicit labels were specified.
+    std::vector<std::string> inputs(args.begin() + 1, args.end());
+
+    UniqueVector<const Target*> target_matches;
+    UniqueVector<const Config*> config_matches;
+    UniqueVector<const Toolchain*> toolchain_matches;
+    UniqueVector<SourceFile> file_matches;
+    if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only,
+                                     &target_matches, &config_matches,
+                                     &toolchain_matches, &file_matches))
+      return 1;
+    matches.insert(matches.begin(), target_matches.begin(),
+                   target_matches.end());
+  } else if (default_toolchain_only) {
+    // List all resolved targets in the default toolchain.
+    for (auto* target : setup->builder().GetAllResolvedTargets()) {
+      if (target->settings()->is_default())
+        matches.push_back(target);
+    }
+  } else {
+    // List all resolved targets.
+    matches = setup->builder().GetAllResolvedTargets();
+  }
+  FilterAndPrintTargets(false, &matches);
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_meta.cc b/src/gn/command_meta.cc
new file mode 100644 (file)
index 0000000..6ee5fe2
--- /dev/null
@@ -0,0 +1,170 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <set>
+
+#include "base/command_line.h"
+#include "base/strings/string_split.h"
+#include "gn/commands.h"
+#include "gn/metadata_walk.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+
+namespace commands {
+
+const char kMeta[] = "meta";
+const char kMeta_HelpShort[] = "meta: List target metadata collection results.";
+const char kMeta_Help[] =
+    R"(gn meta
+
+  gn meta <out_dir> <target>* --data=<key>[,<key>*]* [--walk=<key>[,<key>*]*]
+          [--rebase=<dest dir>]
+
+  Lists collected metaresults of all given targets for the given data key(s),
+  collecting metadata dependencies as specified by the given walk key(s).
+
+  See `gn help generated_file` for more information on the walk.
+
+Arguments
+
+  <target(s)>
+    A list of target labels from which to initiate the walk.
+
+  --data
+    A list of keys from which to extract data. In each target walked, its metadata
+    scope is checked for the presence of these keys. If present, the contents of
+    those variable in the scope are appended to the results list.
+
+  --walk (optional)
+    A list of keys from which to control the walk. In each target walked, its
+    metadata scope is checked for the presence of any of these keys. If present,
+    the contents of those variables is checked to ensure that it is a label of
+    a valid dependency of the target and then added to the set of targets to walk.
+    If the empty string ("") is present in any of these keys, all deps and data_deps
+    are added to the walk set.
+
+  --rebase (optional)
+    A destination directory onto which to rebase any paths found. If set, all
+    collected metadata will be rebased onto this path. This option will throw errors
+    if collected metadata is not a list of strings.
+
+Examples
+
+  gn meta out/Debug "//base/foo" --data=files
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --data=other
+      Lists collected metaresults for the `files` and `other` keys in the
+      //base/foo:foo target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --walk=stop
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of the dependencies listed in the `stop` key (and so on).
+
+  gn meta out/Debug "//base/foo" --data=files --rebase="/"
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree, rebasing the strings in the `files`
+      key onto the source directory of the target's declaration relative to "/".
+)";
+
+int RunMeta(const std::vector<std::string>& args) {
+  if (args.size() == 0) {
+    Err(Location(), "Unknown command format. See \"gn help meta\"",
+        "Usage: \"gn meta <out_dir> <target>* --data=<key>[,<key>*] "
+        "[--walk=<key>[,<key>*]*] [--rebase=<dest dir>]\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false) || !setup->Run())
+    return 1;
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  std::string rebase_dir =
+      cmdline->GetSwitchValueASCII(switches::kMetaRebaseFiles);
+  std::string data_keys_str =
+      cmdline->GetSwitchValueASCII(switches::kMetaDataKeys);
+  std::string walk_keys_str =
+      cmdline->GetSwitchValueASCII(switches::kMetaWalkKeys);
+
+  std::vector<std::string> inputs(args.begin() + 1, args.end());
+
+  UniqueVector<const Target*> targets;
+  for (const auto& input : inputs) {
+    const Target* target = ResolveTargetFromCommandLineString(setup, input);
+    if (!target) {
+      Err(Location(), "Unknown target " + input).PrintToStdout();
+      return 1;
+    }
+    targets.push_back(target);
+  }
+
+  std::vector<std::string> data_keys = base::SplitString(
+      data_keys_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  if (data_keys.empty()) {
+    return 1;
+  }
+  std::vector<std::string> walk_keys = base::SplitString(
+      walk_keys_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  Err err;
+  std::set<const Target*> targets_walked;
+  SourceDir rebase_source_dir(rebase_dir);
+  // When SourceDir constructor is supplied with an empty string,
+  // a trailing slash will be added. This prevent SourceDir::is_null()
+  // from returning true. Explicitly remove this traling slash here.
+  if (rebase_dir.empty()) {
+    rebase_source_dir = SourceDir();
+  }
+  std::vector<Value> result =
+      WalkMetadata(targets, data_keys, walk_keys, rebase_source_dir,
+                   &targets_walked, &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  OutputString("Metadata values\n", DECORATION_DIM);
+  for (const auto& value : result)
+    OutputString("\n" + value.ToString(false) + "\n");
+
+  // TODO(juliehockett): We should have better dep tracing and error support for
+  // this. Also possibly data about where different values came from.
+  OutputString("\nExtracted from:\n", DECORATION_DIM);
+  bool first = true;
+  for (const auto* target : targets_walked) {
+    if (!first) {
+      first = false;
+      OutputString(", ", DECORATION_DIM);
+    }
+    OutputString(target->label().GetUserVisibleName(true) + "\n");
+  }
+  OutputString("\nusing data keys:\n", DECORATION_DIM);
+  first = true;
+  for (const auto& key : data_keys) {
+    if (!first) {
+      first = false;
+      OutputString(", ");
+    }
+    OutputString(key + "\n");
+  }
+  if (!walk_keys.empty()) {
+    OutputString("\nand using walk keys:\n", DECORATION_DIM);
+    first = true;
+    for (const auto& key : walk_keys) {
+      if (!first) {
+        first = false;
+        OutputString(", ");
+      }
+      OutputString(key + "\n");
+    }
+  }
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_outputs.cc b/src/gn/command_outputs.cc
new file mode 100644 (file)
index 0000000..90d1b1e
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "gn/commands.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+
+namespace commands {
+
+const char kOutputs[] = "outputs";
+const char kOutputs_HelpShort[] = "outputs: Which files a source/target make.";
+const char kOutputs_Help[] =
+    R"(gn outputs <out_dir> <list of target or file names...>
+
+  Lists the output files corresponding to the given target(s) or file name(s).
+  There can be multiple outputs because there can be more than one output
+  generated by a build step, and there can be more than one toolchain matched.
+  You can also list multiple inputs which will generate a union of all the
+  outputs from those inputs.
+
+   - The input target/file names are relative to the current directory.
+
+   - The output file names are relative to the root build directory.
+
+   This command is useful for finding a ninja command that will build only a
+   portion of the build.
+
+Target outputs
+
+  If the parameter is a target name that includes a toolchain, it will match
+  only that target in that toolchain. If no toolchain is specified, it will
+  match all targets with that name in any toolchain.
+
+  The result will be the outputs specified by that target which could be a
+  library, executable, output of an action, a stamp file, etc.
+
+File outputs
+
+  If the parameter is a file name it will compute the output for that compile
+  step for all targets in all toolchains that contain that file as a source
+  file.
+
+  If the source is not compiled (e.g. a header or text file), the command will
+  produce no output.
+
+  If the source is listed as an "input" to a binary target or action will
+  resolve to that target's outputs.
+
+Example
+
+  gn outputs out/debug some/directory:some_target
+      Find the outputs of a given target.
+
+  gn outputs out/debug src/project/my_file.cc | xargs ninja -C out/debug
+      Compiles just the given source file in all toolchains it's referenced in.
+
+  git diff --name-only | xargs gn outputs out/x64 | xargs ninja -C out/x64
+      Compiles all files changed in git.
+)";
+
+int RunOutputs(const std::vector<std::string>& args) {
+  if (args.size() < 2) {
+    Err(Location(),
+        "Expected a build dir and one or more input files or targets.\n"
+        "Usage: \"gn outputs <out_dir> <target-or-file>*\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false))
+    return 1;
+  if (!setup->Run())
+    return 1;
+
+  std::vector<std::string> inputs(args.begin() + 1, args.end());
+
+  UniqueVector<const Target*> target_matches;
+  UniqueVector<const Config*> config_matches;
+  UniqueVector<const Toolchain*> toolchain_matches;
+  UniqueVector<SourceFile> file_matches;
+  if (!ResolveFromCommandLineInput(setup, inputs, false, &target_matches,
+                                   &config_matches, &toolchain_matches,
+                                   &file_matches))
+    return 1;
+
+  // We only care about targets and files.
+  if (target_matches.empty() && file_matches.empty()) {
+    Err(Location(), "The input matched no targets or files.").PrintToStdout();
+    return 1;
+  }
+
+  // Resulting outputs.
+  std::vector<OutputFile> outputs;
+
+  // Files. This must go first because it may add to the "targets" list.
+  std::vector<const Target*> all_targets =
+      setup->builder().GetAllResolvedTargets();
+  for (const SourceFile& file : file_matches) {
+    std::vector<TargetContainingFile> targets;
+    GetTargetsContainingFile(setup, all_targets, file, false, &targets);
+    if (targets.empty()) {
+      Err(Location(), base::StringPrintf("No targets reference the file '%s'.",
+                                         file.value().c_str()))
+          .PrintToStdout();
+      return 1;
+    }
+
+    // There can be more than one target that references this file, evaluate the
+    // output name in all of them.
+    for (const TargetContainingFile& pair : targets) {
+      if (pair.second == HowTargetContainsFile::kInputs) {
+        // Inputs maps to the target itself. This will be evaluated below.
+        target_matches.push_back(pair.first);
+      } else if (pair.second == HowTargetContainsFile::kSources) {
+        // Source file, check it.
+        const char* computed_tool = nullptr;
+        std::vector<OutputFile> file_outputs;
+        pair.first->GetOutputFilesForSource(file, &computed_tool,
+                                            &file_outputs);
+        outputs.insert(outputs.end(), file_outputs.begin(), file_outputs.end());
+      }
+    }
+  }
+
+  // Targets.
+  for (const Target* target : target_matches) {
+    std::vector<SourceFile> output_files;
+    Err err;
+    if (!target->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
+                                         &err)) {
+      err.PrintToStdout();
+      return 1;
+    }
+
+    // Convert to OutputFiles.
+    for (const SourceFile& file : output_files)
+      outputs.emplace_back(&setup->build_settings(), file);
+  }
+
+  // Print.
+  for (const OutputFile& output_file : outputs)
+    printf("%s\n", output_file.value().c_str());
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_path.cc b/src/gn/command_path.cc
new file mode 100644 (file)
index 0000000..a82c90d
--- /dev/null
@@ -0,0 +1,413 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "gn/commands.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+
+namespace commands {
+
+namespace {
+
+enum class DepType { NONE, PUBLIC, PRIVATE, DATA };
+
+// The dependency paths are stored in a vector. Assuming the chain:
+//    A --[public]--> B --[private]--> C
+// The stack will look like:
+//    [0] = A, NONE (this has no dep type since nobody depends on it)
+//    [1] = B, PUBLIC
+//    [2] = C, PRIVATE
+using TargetDep = std::pair<const Target*, DepType>;
+using PathVector = std::vector<TargetDep>;
+
+// How to search.
+enum class PrivateDeps { INCLUDE, EXCLUDE };
+enum class DataDeps { INCLUDE, EXCLUDE };
+enum class PrintWhat { ONE, ALL };
+
+struct Options {
+  Options()
+      : print_what(PrintWhat::ONE), public_only(false), with_data(false) {}
+
+  PrintWhat print_what;
+  bool public_only;
+  bool with_data;
+};
+
+using WorkQueue = std::list<PathVector>;
+
+struct Stats {
+  Stats() : public_paths(0), other_paths(0) {}
+
+  int total_paths() const { return public_paths + other_paths; }
+
+  int public_paths;
+  int other_paths;
+
+  // Stores targets that have a path to the destination, and whether that
+  // path is public, private, or data.
+  std::map<const Target*, DepType> found_paths;
+};
+
+// If the implicit_last_dep is not "none", this type indicates the
+// classification of the elided last part of path.
+DepType ClassifyPath(const PathVector& path, DepType implicit_last_dep) {
+  DepType result;
+  if (implicit_last_dep != DepType::NONE)
+    result = implicit_last_dep;
+  else
+    result = DepType::PUBLIC;
+
+  // Skip the 0th one since that is always NONE.
+  for (size_t i = 1; i < path.size(); i++) {
+    // PRIVATE overrides PUBLIC, and DATA overrides everything (the idea is
+    // to find the worst link in the path).
+    if (path[i].second == DepType::PRIVATE) {
+      if (result == DepType::PUBLIC)
+        result = DepType::PRIVATE;
+    } else if (path[i].second == DepType::DATA) {
+      result = DepType::DATA;
+    }
+  }
+  return result;
+}
+
+const char* StringForDepType(DepType type) {
+  switch (type) {
+    case DepType::PUBLIC:
+      return "public";
+    case DepType::PRIVATE:
+      return "private";
+    case DepType::DATA:
+      return "data";
+      break;
+    case DepType::NONE:
+    default:
+      return "";
+  }
+}
+
+// Prints the given path. If the implicit_last_dep is not "none", the last
+// dependency will show an elided dependency with the given annotation.
+void PrintPath(const PathVector& path, DepType implicit_last_dep) {
+  if (path.empty())
+    return;
+
+  // Don't print toolchains unless they differ from the first target.
+  const Label& default_toolchain = path[0].first->label().GetToolchainLabel();
+
+  for (size_t i = 0; i < path.size(); i++) {
+    OutputString(path[i].first->label().GetUserVisibleName(default_toolchain));
+
+    // Output dependency type.
+    if (i == path.size() - 1) {
+      // Last one either gets the implicit last dep type or nothing.
+      if (implicit_last_dep != DepType::NONE) {
+        OutputString(std::string(" --> see ") +
+                         StringForDepType(implicit_last_dep) +
+                         " chain printed above...",
+                     DECORATION_DIM);
+      }
+    } else {
+      // Take type from the next entry.
+      OutputString(
+          std::string(" --[") + StringForDepType(path[i + 1].second) + "]-->",
+          DECORATION_DIM);
+    }
+    OutputString("\n");
+  }
+
+  OutputString("\n");
+}
+
+void InsertTargetsIntoFoundPaths(const PathVector& path,
+                                 DepType implicit_last_dep,
+                                 Stats* stats) {
+  DepType type = ClassifyPath(path, implicit_last_dep);
+
+  bool inserted = false;
+
+  // Don't try to insert the 0th item in the list which is the "from" target.
+  // The search will be run more than once (for the different path types) and
+  // if the "from" target was in the list, subsequent passes could never run
+  // the starting point is already in the list of targets considered).
+  //
+  // One might imagine an alternate implementation where all items are counted
+  // here but the "from" item is erased at the beginning of each search, but
+  // that will mess up the metrics (the private search pass will find the
+  // same public paths as the previous public pass, "inserted" will be true
+  // here since the item wasn't found, and the public path will be
+  // double-counted in the stats.
+  for (size_t i = 1; i < path.size(); i++) {
+    const auto& pair = path[i];
+
+    // Don't overwrite an existing one. The algorithm works by first doing
+    // public, then private, then data, so anything already there is guaranteed
+    // at least as good as our addition.
+    if (stats->found_paths.find(pair.first) == stats->found_paths.end()) {
+      stats->found_paths.insert(std::make_pair(pair.first, type));
+      inserted = true;
+    }
+  }
+
+  if (inserted) {
+    // Only count this path in the stats if any part of it was actually new.
+    if (type == DepType::PUBLIC)
+      stats->public_paths++;
+    else
+      stats->other_paths++;
+  }
+}
+
+void BreadthFirstSearch(const Target* from,
+                        const Target* to,
+                        PrivateDeps private_deps,
+                        DataDeps data_deps,
+                        PrintWhat print_what,
+                        Stats* stats) {
+  // Seed the initial stack with just the "from" target.
+  PathVector initial_stack;
+  initial_stack.emplace_back(from, DepType::NONE);
+  WorkQueue work_queue;
+  work_queue.push_back(initial_stack);
+
+  // Track checked targets to avoid checking the same once more than once.
+  std::set<const Target*> visited;
+
+  while (!work_queue.empty()) {
+    PathVector current_path = work_queue.front();
+    work_queue.pop_front();
+
+    const Target* current_target = current_path.back().first;
+
+    if (current_target == to) {
+      // Found a new path.
+      if (stats->total_paths() == 0 || print_what == PrintWhat::ALL)
+        PrintPath(current_path, DepType::NONE);
+
+      // Insert all nodes on the path into the found paths list. Since we're
+      // doing search breadth first, we know that the current path is the best
+      // path for all nodes on it.
+      InsertTargetsIntoFoundPaths(current_path, DepType::NONE, stats);
+    } else {
+      // Check for a path that connects to an already known-good one. Printing
+      // this here will mean the results aren't strictly in depth-first order
+      // since there could be many items on the found path this connects to.
+      // Doing this here will mean that the output is sorted by length of items
+      // printed (with the redundant parts of the path omitted) rather than
+      // complete path length.
+      const auto& found_current_target =
+          stats->found_paths.find(current_target);
+      if (found_current_target != stats->found_paths.end()) {
+        if (stats->total_paths() == 0 || print_what == PrintWhat::ALL)
+          PrintPath(current_path, found_current_target->second);
+
+        // Insert all nodes on the path into the found paths list since we know
+        // everything along this path also leads to the destination.
+        InsertTargetsIntoFoundPaths(current_path, found_current_target->second,
+                                    stats);
+        continue;
+      }
+    }
+
+    // If we've already checked this one, stop. This should be after the above
+    // check for a known-good check, because known-good ones will always have
+    // been previously visited.
+    if (visited.find(current_target) == visited.end())
+      visited.insert(current_target);
+    else
+      continue;
+
+    // Add public deps for this target to the queue.
+    for (const auto& pair : current_target->public_deps()) {
+      work_queue.push_back(current_path);
+      work_queue.back().push_back(TargetDep(pair.ptr, DepType::PUBLIC));
+    }
+
+    if (private_deps == PrivateDeps::INCLUDE) {
+      // Add private deps.
+      for (const auto& pair : current_target->private_deps()) {
+        work_queue.push_back(current_path);
+        work_queue.back().push_back(TargetDep(pair.ptr, DepType::PRIVATE));
+      }
+    }
+
+    if (data_deps == DataDeps::INCLUDE) {
+      // Add data deps.
+      for (const auto& pair : current_target->data_deps()) {
+        work_queue.push_back(current_path);
+        work_queue.back().push_back(TargetDep(pair.ptr, DepType::DATA));
+      }
+    }
+  }
+}
+
+void DoSearch(const Target* from,
+              const Target* to,
+              const Options& options,
+              Stats* stats) {
+  BreadthFirstSearch(from, to, PrivateDeps::EXCLUDE, DataDeps::EXCLUDE,
+                     options.print_what, stats);
+  if (!options.public_only) {
+    // Check private deps.
+    BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::EXCLUDE,
+                       options.print_what, stats);
+    if (options.with_data) {
+      // Check data deps.
+      BreadthFirstSearch(from, to, PrivateDeps::INCLUDE, DataDeps::INCLUDE,
+                         options.print_what, stats);
+    }
+  }
+}
+
+}  // namespace
+
+const char kPath[] = "path";
+const char kPath_HelpShort[] = "path: Find paths between two targets.";
+const char kPath_Help[] =
+    R"(gn path <out_dir> <target_one> <target_two>
+
+  Finds paths of dependencies between two targets. Each unique path will be
+  printed in one group, and groups will be separate by newlines. The two
+  targets can appear in either order (paths will be found going in either
+  direction).
+
+  By default, a single path will be printed. If there is a path with only
+  public dependencies, the shortest public path will be printed. Otherwise, the
+  shortest path using either public or private dependencies will be printed. If
+  --with-data is specified, data deps will also be considered. If there are
+  multiple shortest paths, an arbitrary one will be selected.
+
+Interesting paths
+
+  In a large project, there can be 100's of millions of unique paths between a
+  very high level and a common low-level target. To make the output more useful
+  (and terminate in a reasonable time), GN will not revisit sub-paths
+  previously known to lead to the target.
+
+Options
+
+  --all
+     Prints all "interesting" paths found rather than just the first one.
+     Public paths will be printed first in order of increasing length, followed
+     by non-public paths in order of increasing length.
+
+  --public
+     Considers only public paths. Can't be used with --with-data.
+
+  --with-data
+     Additionally follows data deps. Without this flag, only public and private
+     linked deps will be followed. Can't be used with --public.
+
+Example
+
+  gn path out/Default //base //gn
+)";
+
+int RunPath(const std::vector<std::string>& args) {
+  if (args.size() != 3) {
+    Err(Location(), "Unknown command format. See \"gn help path\"",
+        "Usage: \"gn path <out_dir> <target_one> <target_two>\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false))
+    return 1;
+  if (!setup->Run())
+    return 1;
+
+  const Target* target1 = ResolveTargetFromCommandLineString(setup, args[1]);
+  if (!target1)
+    return 1;
+  const Target* target2 = ResolveTargetFromCommandLineString(setup, args[2]);
+  if (!target2)
+    return 1;
+
+  Options options;
+  options.print_what = base::CommandLine::ForCurrentProcess()->HasSwitch("all")
+                           ? PrintWhat::ALL
+                           : PrintWhat::ONE;
+  options.public_only =
+      base::CommandLine::ForCurrentProcess()->HasSwitch("public");
+  options.with_data =
+      base::CommandLine::ForCurrentProcess()->HasSwitch("with-data");
+  if (options.public_only && options.with_data) {
+    Err(Location(), "Can't use --public with --with-data for 'gn path'.",
+        "Your zealous over-use of arguments has inevitably resulted in an "
+        "invalid\ncombination of flags.")
+        .PrintToStdout();
+    return 1;
+  }
+
+  Stats stats;
+  DoSearch(target1, target2, options, &stats);
+  if (stats.total_paths() == 0) {
+    // If we don't find a path going "forwards", try the reverse direction.
+    // Deps can only go in one direction without having a cycle, which will
+    // have caused a run failure above.
+    DoSearch(target2, target1, options, &stats);
+  }
+
+  // This string is inserted in the results to annotate whether the result
+  // is only public or includes data deps or not.
+  const char* path_annotation = "";
+  if (options.public_only)
+    path_annotation = "public ";
+  else if (!options.with_data)
+    path_annotation = "non-data ";
+
+  if (stats.total_paths() == 0) {
+    // No results.
+    OutputString(
+        base::StringPrintf("No %spaths found between these two targets.\n",
+                           path_annotation),
+        DECORATION_YELLOW);
+  } else if (stats.total_paths() == 1) {
+    // Exactly one result.
+    OutputString(base::StringPrintf("1 %spath found.", path_annotation),
+                 DECORATION_YELLOW);
+    if (!options.public_only) {
+      if (stats.public_paths)
+        OutputString(" It is public.");
+      else
+        OutputString(" It is not public.");
+    }
+    OutputString("\n");
+  } else {
+    if (options.print_what == PrintWhat::ALL) {
+      // Showing all paths when there are many.
+      OutputString(base::StringPrintf("%d \"interesting\" %spaths found.",
+                                      stats.total_paths(), path_annotation),
+                   DECORATION_YELLOW);
+      if (!options.public_only) {
+        OutputString(
+            base::StringPrintf(" %d of them are public.", stats.public_paths));
+      }
+      OutputString("\n");
+    } else {
+      // Showing one path when there are many.
+      OutputString(
+          base::StringPrintf("Showing one of %d \"interesting\" %spaths.",
+                             stats.total_paths(), path_annotation),
+          DECORATION_YELLOW);
+      if (!options.public_only) {
+        OutputString(
+            base::StringPrintf(" %d of them are public.", stats.public_paths));
+      }
+      OutputString("\nUse --all to print all paths.\n");
+    }
+  }
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/command_refs.cc b/src/gn/command_refs.cc
new file mode 100644 (file)
index 0000000..dc27f56
--- /dev/null
@@ -0,0 +1,453 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "gn/commands.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file.h"
+#include "gn/item.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+
+namespace commands {
+
+namespace {
+
+using TargetSet = std::set<const Target*>;
+using TargetVector = std::vector<const Target*>;
+
+// Maps targets to the list of targets that depend on them.
+using DepMap = std::multimap<const Target*, const Target*>;
+
+// Populates the reverse dependency map for the targets in the Setup.
+void FillDepMap(Setup* setup, DepMap* dep_map) {
+  for (auto* target : setup->builder().GetAllResolvedTargets()) {
+    for (const auto& dep_pair : target->GetDeps(Target::DEPS_ALL))
+      dep_map->insert(std::make_pair(dep_pair.ptr, target));
+  }
+}
+
+// Forward declaration for function below.
+size_t RecursivePrintTargetDeps(const DepMap& dep_map,
+                                const Target* target,
+                                TargetSet* seen_targets,
+                                int indent_level);
+
+// Prints the target and its dependencies in tree form. If the set is non-null,
+// new targets encountered will be added to the set, and if a ref is in the set
+// already, it will not be recused into. When the set is null, all refs will be
+// printed.
+//
+// Returns the number of items printed.
+size_t RecursivePrintTarget(const DepMap& dep_map,
+                            const Target* target,
+                            TargetSet* seen_targets,
+                            int indent_level) {
+  std::string indent(indent_level * 2, ' ');
+  size_t count = 1;
+
+  // Only print the toolchain for non-default-toolchain targets.
+  OutputString(indent + target->label().GetUserVisibleName(
+                            !target->settings()->is_default()));
+
+  bool print_children = true;
+  if (seen_targets) {
+    if (seen_targets->find(target) == seen_targets->end()) {
+      // New target, mark it visited.
+      seen_targets->insert(target);
+    } else {
+      // Already seen.
+      print_children = false;
+      // Only print "..." if something is actually elided, which means that
+      // the current target has children.
+      if (dep_map.lower_bound(target) != dep_map.upper_bound(target))
+        OutputString("...");
+    }
+  }
+
+  OutputString("\n");
+  if (print_children) {
+    count += RecursivePrintTargetDeps(dep_map, target, seen_targets,
+                                      indent_level + 1);
+  }
+  return count;
+}
+
+// Prints refs of the given target (not the target itself). See
+// RecursivePrintTarget.
+size_t RecursivePrintTargetDeps(const DepMap& dep_map,
+                                const Target* target,
+                                TargetSet* seen_targets,
+                                int indent_level) {
+  DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
+  DepMap::const_iterator dep_end = dep_map.upper_bound(target);
+  size_t count = 0;
+  for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
+       cur_dep++) {
+    count += RecursivePrintTarget(dep_map, cur_dep->second, seen_targets,
+                                  indent_level);
+  }
+  return count;
+}
+
+void RecursiveCollectChildRefs(const DepMap& dep_map,
+                               const Target* target,
+                               TargetSet* results);
+
+// Recursively finds all targets that reference the given one, and additionally
+// adds the current one to the list.
+void RecursiveCollectRefs(const DepMap& dep_map,
+                          const Target* target,
+                          TargetSet* results) {
+  if (results->find(target) != results->end())
+    return;  // Already found this target.
+  results->insert(target);
+  RecursiveCollectChildRefs(dep_map, target, results);
+}
+
+// Recursively finds all targets that reference the given one.
+void RecursiveCollectChildRefs(const DepMap& dep_map,
+                               const Target* target,
+                               TargetSet* results) {
+  DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
+  DepMap::const_iterator dep_end = dep_map.upper_bound(target);
+  for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
+       cur_dep++)
+    RecursiveCollectRefs(dep_map, cur_dep->second, results);
+}
+
+bool TargetReferencesConfig(const Target* target, const Config* config) {
+  for (const LabelConfigPair& cur : target->configs()) {
+    if (cur.ptr == config)
+      return true;
+  }
+  for (const LabelConfigPair& cur : target->public_configs()) {
+    if (cur.ptr == config)
+      return true;
+  }
+  return false;
+}
+
+void GetTargetsReferencingConfig(Setup* setup,
+                                 const std::vector<const Target*>& all_targets,
+                                 const Config* config,
+                                 bool default_toolchain_only,
+                                 UniqueVector<const Target*>* matches) {
+  Label default_toolchain = setup->loader()->default_toolchain_label();
+  for (auto* target : all_targets) {
+    if (default_toolchain_only) {
+      // Only check targets in the default toolchain.
+      if (target->label().GetToolchainLabel() != default_toolchain)
+        continue;
+    }
+    if (TargetReferencesConfig(target, config))
+      matches->push_back(target);
+  }
+}
+
+// Returns the number of matches printed.
+size_t DoTreeOutput(const DepMap& dep_map,
+                    const UniqueVector<const Target*>& implicit_target_matches,
+                    const UniqueVector<const Target*>& explicit_target_matches,
+                    bool all) {
+  TargetSet seen_targets;
+  size_t count = 0;
+
+  // Implicit targets don't get printed themselves.
+  for (const Target* target : implicit_target_matches) {
+    if (all)
+      count += RecursivePrintTargetDeps(dep_map, target, nullptr, 0);
+    else
+      count += RecursivePrintTargetDeps(dep_map, target, &seen_targets, 0);
+  }
+
+  // Explicit targets appear in the output.
+  for (const Target* target : implicit_target_matches) {
+    if (all)
+      count += RecursivePrintTarget(dep_map, target, nullptr, 0);
+    else
+      count += RecursivePrintTarget(dep_map, target, &seen_targets, 0);
+  }
+
+  return count;
+}
+
+// Returns the number of matches printed.
+size_t DoAllListOutput(
+    const DepMap& dep_map,
+    const UniqueVector<const Target*>& implicit_target_matches,
+    const UniqueVector<const Target*>& explicit_target_matches) {
+  // Output recursive dependencies, uniquified and flattened.
+  TargetSet results;
+
+  for (const Target* target : implicit_target_matches)
+    RecursiveCollectChildRefs(dep_map, target, &results);
+  for (const Target* target : explicit_target_matches) {
+    // Explicit targets also get added to the output themselves.
+    results.insert(target);
+    RecursiveCollectChildRefs(dep_map, target, &results);
+  }
+
+  FilterAndPrintTargetSet(false, results);
+  return results.size();
+}
+
+// Returns the number of matches printed.
+size_t DoDirectListOutput(
+    const DepMap& dep_map,
+    const UniqueVector<const Target*>& implicit_target_matches,
+    const UniqueVector<const Target*>& explicit_target_matches) {
+  TargetSet results;
+
+  // Output everything that refers to the implicit ones.
+  for (const Target* target : implicit_target_matches) {
+    DepMap::const_iterator dep_begin = dep_map.lower_bound(target);
+    DepMap::const_iterator dep_end = dep_map.upper_bound(target);
+    for (DepMap::const_iterator cur_dep = dep_begin; cur_dep != dep_end;
+         cur_dep++)
+      results.insert(cur_dep->second);
+  }
+
+  // And just output the explicit ones directly (these are the target matches
+  // when referring to what references a file or config).
+  for (const Target* target : explicit_target_matches)
+    results.insert(target);
+
+  FilterAndPrintTargetSet(false, results);
+  return results.size();
+}
+
+}  // namespace
+
+const char kRefs[] = "refs";
+const char kRefs_HelpShort[] = "refs: Find stuff referencing a target or file.";
+const char kRefs_Help[] =
+    R"(gn refs
+
+  gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* [--all]
+          [--default-toolchain] [--as=...] [--testonly=...] [--type=...]
+
+  Finds reverse dependencies (which targets reference something). The input is
+  a list containing:
+
+   - Target label: The result will be which targets depend on it.
+
+   - Config label: The result will be which targets list the given config in
+     its "configs" or "public_configs" list.
+
+   - Label pattern: The result will be which targets depend on any target
+     matching the given pattern. Patterns will not match configs. These are not
+     general regular expressions, see "gn help label_pattern" for details.
+
+   - File name: The result will be which targets list the given file in its
+     "inputs", "sources", "public", "data", or "outputs". Any input that does
+     not contain wildcards and does not match a target or a config will be
+     treated as a file.
+
+   - Response file: If the input starts with an "@", it will be interpreted as
+     a path to a file containing a list of labels or file names, one per line.
+     This allows us to handle long lists of inputs without worrying about
+     command line limits.
+
+Options
+
+  --all
+      When used without --tree, will recurse and display all unique
+      dependencies of the given targets. For example, if the input is a target,
+      this will output all targets that depend directly or indirectly on the
+      input. If the input is a file, this will output all targets that depend
+      directly or indirectly on that file.
+
+      When used with --tree, turns off eliding to show a complete tree.
+
+)"
+
+    TARGET_PRINTING_MODE_COMMAND_LINE_HELP "\n" DEFAULT_TOOLCHAIN_SWITCH_HELP
+
+    R"(
+  -q
+     Quiet. If nothing matches, don't print any output. Without this option, if
+     there are no matches there will be an informational message printed which
+     might interfere with scripts processing the output.
+
+)"
+
+    TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
+
+    R"(
+  --tree
+      Outputs a reverse dependency tree from the given target. Duplicates will
+      be elided. Combine with --all to see a full dependency tree.
+
+      Tree output can not be used with the filtering or output flags: --as,
+      --type, --testonly.
+
+)"
+
+    TARGET_TYPE_FILTER_COMMAND_LINE_HELP
+
+    R"(
+
+Examples (target input)
+
+  gn refs out/Debug //gn:gn
+      Find all targets depending on the given exact target name.
+
+  gn refs out/Debug //base:i18n --as=buildfiles | xargs gvim
+      Edit all .gn files containing references to //base:i18n
+
+  gn refs out/Debug //base --all
+      List all targets depending directly or indirectly on //base:base.
+
+  gn refs out/Debug "//base/*"
+      List all targets depending directly on any target in //base or
+      its subdirectories.
+
+  gn refs out/Debug "//base:*"
+      List all targets depending directly on any target in
+      //base/BUILD.gn.
+
+  gn refs out/Debug //base --tree
+      Print a reverse dependency tree of //base:base
+
+Examples (file input)
+
+  gn refs out/Debug //base/macros.h
+      Print target(s) listing //base/macros.h as a source.
+
+  gn refs out/Debug //base/macros.h --tree
+      Display a reverse dependency tree to get to the given file. This
+      will show how dependencies will reference that file.
+
+  gn refs out/Debug //base/macros.h //base/at_exit.h --all
+      Display all unique targets with some dependency path to a target
+      containing either of the given files as a source.
+
+  gn refs out/Debug //base/macros.h --testonly=true --type=executable
+          --all --as=output
+      Display the executable file names of all test executables
+      potentially affected by a change to the given file.
+)";
+
+int RunRefs(const std::vector<std::string>& args) {
+  if (args.size() <= 1) {
+    Err(Location(), "Unknown command format. See \"gn help refs\"",
+        "Usage: \"gn refs <out_dir> (<label_pattern>|<file>)*\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool tree = cmdline->HasSwitch("tree");
+  bool all = cmdline->HasSwitch("all");
+  bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain);
+
+  // Deliberately leaked to avoid expensive process teardown.
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false) || !setup->Run())
+    return 1;
+
+  // The inputs are everything but the first arg (which is the build dir).
+  std::vector<std::string> inputs;
+  for (size_t i = 1; i < args.size(); i++) {
+    if (args[i][0] == '@') {
+      // The argument is as a path to a response file.
+      std::string contents;
+      bool ret =
+          base::ReadFileToString(UTF8ToFilePath(args[i].substr(1)), &contents);
+      if (!ret) {
+        Err(Location(), "Response file " + args[i].substr(1) + " not found.")
+            .PrintToStdout();
+        return 1;
+      }
+      for (const std::string& line : base::SplitString(
+               contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+        if (!line.empty())
+          inputs.push_back(line);
+      }
+    } else {
+      // The argument is a label or a path.
+      inputs.push_back(args[i]);
+    }
+  }
+
+  // Get the matches for the command-line input.
+  UniqueVector<const Target*> target_matches;
+  UniqueVector<const Config*> config_matches;
+  UniqueVector<const Toolchain*> toolchain_matches;
+  UniqueVector<SourceFile> file_matches;
+  if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only,
+                                   &target_matches, &config_matches,
+                                   &toolchain_matches, &file_matches))
+    return 1;
+
+  // When you give a file or config as an input, you want the targets that are
+  // associated with it. We don't want to just append this to the
+  // target_matches, however, since these targets should actually be listed in
+  // the output, while for normal targets you don't want to see the inputs,
+  // only what refers to them.
+  std::vector<const Target*> all_targets =
+      setup->builder().GetAllResolvedTargets();
+  UniqueVector<const Target*> explicit_target_matches;
+  for (const auto& file : file_matches) {
+    std::vector<TargetContainingFile> target_containing;
+    GetTargetsContainingFile(setup, all_targets, file, default_toolchain_only,
+                             &target_containing);
+
+    // Extract just the Target*.
+    for (const TargetContainingFile& pair : target_containing)
+      explicit_target_matches.push_back(pair.first);
+  }
+  for (auto* config : config_matches) {
+    GetTargetsReferencingConfig(setup, all_targets, config,
+                                default_toolchain_only,
+                                &explicit_target_matches);
+  }
+
+  // Tell the user if their input matches no files or labels. We need to check
+  // both that it matched no targets and no configs. File input will already
+  // have been converted to targets at this point. Configs will have been
+  // converted to targets also, but there could be no targets referencing the
+  // config, which is different than no config with that name.
+  bool quiet = cmdline->HasSwitch("q");
+  if (!quiet && config_matches.empty() && explicit_target_matches.empty() &&
+      target_matches.empty()) {
+    OutputString("The input matches no targets, configs, or files.\n",
+                 DECORATION_YELLOW);
+    return 1;
+  }
+
+  // Construct the reverse dependency tree.
+  DepMap dep_map;
+  FillDepMap(setup, &dep_map);
+
+  size_t cnt = 0;
+  if (tree)
+    cnt = DoTreeOutput(dep_map, target_matches, explicit_target_matches, all);
+  else if (all)
+    cnt = DoAllListOutput(dep_map, target_matches, explicit_target_matches);
+  else
+    cnt = DoDirectListOutput(dep_map, target_matches, explicit_target_matches);
+
+  // If you ask for the references of a valid target, but that target has
+  // nothing referencing it, we'll get here without having printed anything.
+  if (!quiet && cnt == 0)
+    OutputString("Nothing references this.\n", DECORATION_YELLOW);
+
+  return 0;
+}
+
+}  // namespace commands
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
new file mode 100644 (file)
index 0000000..da6514b
--- /dev/null
@@ -0,0 +1,644 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/commands.h"
+
+#include <optional>
+
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "gn/builder.h"
+#include "gn/config_values_extractors.h"
+#include "gn/filesystem_utils.h"
+#include "gn/item.h"
+#include "gn/label.h"
+#include "gn/label_pattern.h"
+#include "gn/setup.h"
+#include "gn/standard_out.h"
+#include "gn/target.h"
+#include "util/build_config.h"
+
+namespace commands {
+
+namespace {
+
+// Like above but the input string can be a pattern that matches multiple
+// targets. If the input does not parse as a pattern, prints and error and
+// returns false. If the pattern is valid, fills the vector (which might be
+// empty if there are no matches) and returns true.
+//
+// If default_toolchain_only is true, a pattern with an unspecified toolchain
+// will match the default toolchain only. If true, all toolchains will be
+// matched.
+bool ResolveTargetsFromCommandLinePattern(Setup* setup,
+                                          const std::string& label_pattern,
+                                          bool default_toolchain_only,
+                                          std::vector<const Target*>* matches) {
+  Value pattern_value(nullptr, label_pattern);
+
+  Err err;
+  LabelPattern pattern = LabelPattern::GetPattern(
+      SourceDirForCurrentDirectory(setup->build_settings().root_path()),
+      setup->build_settings().root_path_utf8(), pattern_value, &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return false;
+  }
+
+  if (default_toolchain_only) {
+    // By default a pattern with an empty toolchain will match all toolchains.
+    // If the caller wants to default to the main toolchain only, set it
+    // explicitly.
+    if (pattern.toolchain().is_null()) {
+      // No explicit toolchain set.
+      pattern.set_toolchain(setup->loader()->default_toolchain_label());
+    }
+  }
+
+  std::vector<LabelPattern> pattern_vector;
+  pattern_vector.push_back(pattern);
+  FilterTargetsByPatterns(setup->builder().GetAllResolvedTargets(),
+                          pattern_vector, matches);
+  return true;
+}
+
+// If there's an error, it will be printed and false will be returned.
+bool ResolveStringFromCommandLineInput(
+    Setup* setup,
+    const SourceDir& current_dir,
+    const std::string& input,
+    bool default_toolchain_only,
+    UniqueVector<const Target*>* target_matches,
+    UniqueVector<const Config*>* config_matches,
+    UniqueVector<const Toolchain*>* toolchain_matches,
+    UniqueVector<SourceFile>* file_matches) {
+  if (LabelPattern::HasWildcard(input)) {
+    // For now, only match patterns against targets. It might be nice in the
+    // future to allow the user to specify which types of things they want to
+    // match, but it should probably only match targets by default.
+    std::vector<const Target*> target_match_vector;
+    if (!ResolveTargetsFromCommandLinePattern(
+            setup, input, default_toolchain_only, &target_match_vector))
+      return false;
+    for (const Target* target : target_match_vector)
+      target_matches->push_back(target);
+    return true;
+  }
+
+  // Try to figure out what this thing is.
+  Err err;
+  Label label = Label::Resolve(
+      current_dir, setup->build_settings().root_path_utf8(),
+      setup->loader()->default_toolchain_label(), Value(nullptr, input), &err);
+  if (err.has_error()) {
+    // Not a valid label, assume this must be a file.
+    err = Err();
+    file_matches->push_back(current_dir.ResolveRelativeFile(
+        Value(nullptr, input), &err, setup->build_settings().root_path_utf8()));
+    if (err.has_error()) {
+      err.PrintToStdout();
+      return false;
+    }
+    return true;
+  }
+
+  const Item* item = setup->builder().GetItem(label);
+  if (item) {
+    if (const Config* as_config = item->AsConfig())
+      config_matches->push_back(as_config);
+    else if (const Target* as_target = item->AsTarget())
+      target_matches->push_back(as_target);
+    else if (const Toolchain* as_toolchain = item->AsToolchain())
+      toolchain_matches->push_back(as_toolchain);
+  } else {
+    // Not an item, assume this must be a file.
+    file_matches->push_back(current_dir.ResolveRelativeFile(
+        Value(nullptr, input), &err, setup->build_settings().root_path_utf8()));
+    if (err.has_error()) {
+      err.PrintToStdout();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+enum TargetPrintingMode {
+  TARGET_PRINT_BUILDFILE,
+  TARGET_PRINT_LABEL,
+  TARGET_PRINT_OUTPUT,
+};
+
+// Retrieves the target printing mode based on the command line flags for the
+// current process. Returns true on success. On error, prints a message to the
+// console and returns false.
+bool GetTargetPrintingMode(TargetPrintingMode* mode) {
+  std::string switch_key = "as";
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+
+  if (!cmdline->HasSwitch(switch_key)) {
+    // Default to labels.
+    *mode = TARGET_PRINT_LABEL;
+    return true;
+  }
+
+  std::string value = cmdline->GetSwitchValueASCII(switch_key);
+  if (value == "buildfile") {
+    *mode = TARGET_PRINT_BUILDFILE;
+    return true;
+  }
+  if (value == "label") {
+    *mode = TARGET_PRINT_LABEL;
+    return true;
+  }
+  if (value == "output") {
+    *mode = TARGET_PRINT_OUTPUT;
+    return true;
+  }
+
+  Err(Location(), "Invalid value for \"--as\".",
+      "I was expecting \"buildfile\", \"label\", or \"output\" but you\n"
+      "said \"" +
+          value + "\".")
+      .PrintToStdout();
+  return false;
+}
+
+// Returns the target type filter based on the command line flags for the
+// current process. Returns true on success. On error, prints a message to the
+// console and returns false.
+//
+// Target::UNKNOWN will be set if there is no filter. Target::ACTION_FOREACH
+// will never be returned. Code applying the filters should apply Target::ACTION
+// to both ACTION and ACTION_FOREACH.
+bool GetTargetTypeFilter(Target::OutputType* type) {
+  std::string switch_key = "type";
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+
+  if (!cmdline->HasSwitch(switch_key)) {
+    // Default to unknown -> no filtering.
+    *type = Target::UNKNOWN;
+    return true;
+  }
+
+  std::string value = cmdline->GetSwitchValueASCII(switch_key);
+  if (value == "group") {
+    *type = Target::GROUP;
+    return true;
+  }
+  if (value == "executable") {
+    *type = Target::EXECUTABLE;
+    return true;
+  }
+  if (value == "shared_library") {
+    *type = Target::SHARED_LIBRARY;
+    return true;
+  }
+  if (value == "loadable_module") {
+    *type = Target::LOADABLE_MODULE;
+    return true;
+  }
+  if (value == "static_library") {
+    *type = Target::STATIC_LIBRARY;
+    return true;
+  }
+  if (value == "source_set") {
+    *type = Target::SOURCE_SET;
+    return true;
+  }
+  if (value == "copy") {
+    *type = Target::COPY_FILES;
+    return true;
+  }
+  if (value == "action") {
+    *type = Target::ACTION;
+    return true;
+  }
+
+  Err(Location(), "Invalid value for \"--type\".").PrintToStdout();
+  return false;
+}
+
+// Applies any testonly filtering specified on the command line to the given
+// target set. On failure, prints an error and returns false.
+bool ApplyTestonlyFilter(std::vector<const Target*>* targets) {
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  std::string testonly_key = "testonly";
+
+  if (targets->empty() || !cmdline->HasSwitch(testonly_key))
+    return true;
+
+  std::string testonly_value = cmdline->GetSwitchValueASCII(testonly_key);
+  bool testonly = false;
+  if (testonly_value == "true") {
+    testonly = true;
+  } else if (testonly_value != "false") {
+    Err(Location(), "Bad value for --testonly.",
+        "I was expecting --testonly=true or --testonly=false.")
+        .PrintToStdout();
+    return false;
+  }
+
+  // Filter into a copy of the vector, then replace the output.
+  std::vector<const Target*> result;
+  result.reserve(targets->size());
+
+  for (const Target* target : *targets) {
+    if (target->testonly() == testonly)
+      result.push_back(target);
+  }
+
+  *targets = std::move(result);
+  return true;
+}
+
+// Applies any target type filtering specified on the command line to the given
+// target set. On failure, prints an error and returns false.
+bool ApplyTypeFilter(std::vector<const Target*>* targets) {
+  Target::OutputType type = Target::UNKNOWN;
+  if (!GetTargetTypeFilter(&type))
+    return false;
+  if (targets->empty() || type == Target::UNKNOWN)
+    return true;  // Nothing to filter out.
+
+  // Filter into a copy of the vector, then replace the output.
+  std::vector<const Target*> result;
+  result.reserve(targets->size());
+
+  for (const Target* target : *targets) {
+    // Make "action" also apply to ACTION_FOREACH.
+    if (target->output_type() == type ||
+        (type == Target::ACTION &&
+         target->output_type() == Target::ACTION_FOREACH))
+      result.push_back(target);
+  }
+
+  *targets = std::move(result);
+  return true;
+}
+
+// Returns the file path generating this item.
+base::FilePath BuildFileForItem(const Item* item) {
+  return item->defined_from()->GetRange().begin().file()->physical_name();
+}
+
+void PrintTargetsAsBuildfiles(const std::vector<const Target*>& targets,
+                              base::ListValue* out) {
+  // Output the set of unique source files.
+  std::set<std::string> unique_files;
+  for (const Target* target : targets)
+    unique_files.insert(FilePathToUTF8(BuildFileForItem(target)));
+
+  for (const std::string& file : unique_files) {
+    out->AppendString(file);
+  }
+}
+
+void PrintTargetsAsLabels(const std::vector<const Target*>& targets,
+                          base::ListValue* out) {
+  // Putting the labels into a set automatically sorts them for us.
+  std::set<Label> unique_labels;
+  for (auto* target : targets)
+    unique_labels.insert(target->label());
+
+  // Grab the label of the default toolchain from the first target.
+  Label default_tc_label = targets[0]->settings()->default_toolchain_label();
+
+  for (const Label& label : unique_labels) {
+    // Print toolchain only for ones not in the default toolchain.
+    out->AppendString(label.GetUserVisibleName(label.GetToolchainLabel() !=
+                                               default_tc_label));
+  }
+}
+
+void PrintTargetsAsOutputs(const std::vector<const Target*>& targets,
+                           base::ListValue* out) {
+  if (targets.empty())
+    return;
+
+  // Grab the build settings from a random target.
+  const BuildSettings* build_settings =
+      targets[0]->settings()->build_settings();
+
+  for (const Target* target : targets) {
+    // Use the link output file if there is one, otherwise fall back to the
+    // dependency output file (for actions, for example).
+    OutputFile output_file = target->link_output_file();
+    if (output_file.value().empty())
+      output_file = target->dependency_output_file();
+
+    SourceFile output_as_source = output_file.AsSourceFile(build_settings);
+    std::string result =
+        RebasePath(output_as_source.value(), build_settings->build_dir(),
+                   build_settings->root_path_utf8());
+    out->AppendString(result);
+  }
+}
+
+#if defined(OS_WIN)
+// Git bash will remove the first "/" in "//" paths
+// This also happens for labels assigned to command line parameters, e.g.
+// --filters
+// Fix "//" paths, but not absolute and relative paths
+inline std::string FixGitBashLabelEdit(const std::string& label) {
+  static std::unique_ptr<base::Environment> git_bash_env;
+  if (!git_bash_env)
+    git_bash_env = base::Environment::Create();
+
+  std::string temp_label(label);
+
+  if (git_bash_env->HasVar(
+          "MSYSTEM") &&        // Only for MinGW based shells like Git Bash
+      temp_label[0] == '/' &&  // Only fix for //foo paths, not /f:oo paths
+      (temp_label.length() < 2 ||
+       (temp_label[1] != '/' &&
+        (temp_label.length() < 3 || temp_label[1] != ':'))))
+    temp_label.insert(0, "/");
+  return temp_label;
+}
+#else
+// Only repair on Windows
+inline std::string FixGitBashLabelEdit(const std::string& label) {
+  return label;
+}
+#endif
+
+std::optional<HowTargetContainsFile> TargetContainsFile(
+    const Target* target,
+    const SourceFile& file) {
+  for (const auto& cur_file : target->sources()) {
+    if (cur_file == file)
+      return HowTargetContainsFile::kSources;
+  }
+  for (const auto& cur_file : target->public_headers()) {
+    if (cur_file == file)
+      return HowTargetContainsFile::kPublic;
+  }
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    for (const auto& cur_file : iter.cur().inputs()) {
+      if (cur_file == file)
+        return HowTargetContainsFile::kInputs;
+    }
+  }
+  for (const auto& cur_file : target->data()) {
+    if (cur_file == file.value())
+      return HowTargetContainsFile::kData;
+    if (cur_file.back() == '/' &&
+        base::StartsWith(file.value(), cur_file, base::CompareCase::SENSITIVE))
+      return HowTargetContainsFile::kData;
+  }
+
+  if (target->action_values().script().value() == file.value())
+    return HowTargetContainsFile::kScript;
+
+  std::vector<SourceFile> output_sources;
+  target->action_values().GetOutputsAsSourceFiles(target, &output_sources);
+  for (const auto& cur_file : output_sources) {
+    if (cur_file == file)
+      return HowTargetContainsFile::kOutput;
+  }
+
+  for (const auto& cur_file : target->computed_outputs()) {
+    if (cur_file.AsSourceFile(target->settings()->build_settings()) == file)
+      return HowTargetContainsFile::kOutput;
+  }
+  return std::nullopt;
+}
+
+}  // namespace
+
+CommandInfo::CommandInfo()
+    : help_short(nullptr), help(nullptr), runner(nullptr) {}
+
+CommandInfo::CommandInfo(const char* in_help_short,
+                         const char* in_help,
+                         CommandRunner in_runner)
+    : help_short(in_help_short), help(in_help), runner(in_runner) {}
+
+const CommandInfoMap& GetCommands() {
+  static CommandInfoMap info_map;
+  if (info_map.empty()) {
+#define INSERT_COMMAND(cmd) \
+  info_map[k##cmd] = CommandInfo(k##cmd##_HelpShort, k##cmd##_Help, &Run##cmd);
+
+    INSERT_COMMAND(Analyze)
+    INSERT_COMMAND(Args)
+    INSERT_COMMAND(Check)
+    INSERT_COMMAND(Clean)
+    INSERT_COMMAND(Desc)
+    INSERT_COMMAND(Gen)
+    INSERT_COMMAND(Format)
+    INSERT_COMMAND(Help)
+    INSERT_COMMAND(Meta)
+    INSERT_COMMAND(Ls)
+    INSERT_COMMAND(Outputs)
+    INSERT_COMMAND(Path)
+    INSERT_COMMAND(Refs)
+    INSERT_COMMAND(CleanStale);
+
+#undef INSERT_COMMAND
+  }
+  return info_map;
+}
+
+const Target* ResolveTargetFromCommandLineString(
+    Setup* setup,
+    const std::string& label_string) {
+  // Need to resolve the label after we know the default toolchain.
+  Label default_toolchain = setup->loader()->default_toolchain_label();
+  Value arg_value(nullptr, FixGitBashLabelEdit(label_string));
+  Err err;
+  Label label = Label::Resolve(
+      SourceDirForCurrentDirectory(setup->build_settings().root_path()),
+      setup->build_settings().root_path_utf8(), default_toolchain, arg_value,
+      &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return nullptr;
+  }
+
+  const Item* item = setup->builder().GetItem(label);
+  if (!item) {
+    Err(Location(), "Label not found.",
+        label.GetUserVisibleName(false) + " not found.")
+        .PrintToStdout();
+    return nullptr;
+  }
+
+  const Target* target = item->AsTarget();
+  if (!target) {
+    Err(Location(), "Not a target.",
+        "The \"" + label.GetUserVisibleName(false) +
+            "\" thing\n"
+            "is not a target. Somebody should probably implement this command "
+            "for "
+            "other\nitem types.")
+        .PrintToStdout();
+    return nullptr;
+  }
+
+  return target;
+}
+
+bool ResolveFromCommandLineInput(
+    Setup* setup,
+    const std::vector<std::string>& input,
+    bool default_toolchain_only,
+    UniqueVector<const Target*>* target_matches,
+    UniqueVector<const Config*>* config_matches,
+    UniqueVector<const Toolchain*>* toolchain_matches,
+    UniqueVector<SourceFile>* file_matches) {
+  if (input.empty()) {
+    Err(Location(), "You need to specify a label, file, or pattern.")
+        .PrintToStdout();
+    return false;
+  }
+
+  SourceDir cur_dir =
+      SourceDirForCurrentDirectory(setup->build_settings().root_path());
+  for (const auto& cur : input) {
+    if (!ResolveStringFromCommandLineInput(
+            setup, cur_dir, cur, default_toolchain_only, target_matches,
+            config_matches, toolchain_matches, file_matches))
+      return false;
+  }
+  return true;
+}
+
+void FilterTargetsByPatterns(const std::vector<const Target*>& input,
+                             const std::vector<LabelPattern>& filter,
+                             std::vector<const Target*>* output) {
+  for (auto* target : input) {
+    for (const auto& pattern : filter) {
+      if (pattern.Matches(target->label())) {
+        output->push_back(target);
+        break;
+      }
+    }
+  }
+}
+
+void FilterTargetsByPatterns(const std::vector<const Target*>& input,
+                             const std::vector<LabelPattern>& filter,
+                             UniqueVector<const Target*>* output) {
+  for (auto* target : input) {
+    for (const auto& pattern : filter) {
+      if (pattern.Matches(target->label())) {
+        output->push_back(target);
+        break;
+      }
+    }
+  }
+}
+
+void FilterOutTargetsByPatterns(const std::vector<const Target*>& input,
+                                const std::vector<LabelPattern>& filter,
+                                std::vector<const Target*>* output) {
+  for (auto* target : input) {
+    bool match = false;
+    for (const auto& pattern : filter) {
+      if (pattern.Matches(target->label())) {
+        match = true;
+        break;
+      }
+    }
+    if (!match) {
+      output->push_back(target);
+    }
+  }
+}
+
+bool FilterPatternsFromString(const BuildSettings* build_settings,
+                              const std::string& label_list_string,
+                              std::vector<LabelPattern>* filters,
+                              Err* err) {
+  std::vector<std::string> tokens = base::SplitString(
+      label_list_string, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  SourceDir root_dir("//");
+
+  filters->reserve(tokens.size());
+  for (const std::string& token : tokens) {
+    LabelPattern pattern = LabelPattern::GetPattern(
+        root_dir, build_settings->root_path_utf8(),
+        Value(nullptr, FixGitBashLabelEdit(token)), err);
+    if (err->has_error())
+      return false;
+    filters->push_back(pattern);
+  }
+
+  return true;
+}
+
+void FilterAndPrintTargets(std::vector<const Target*>* targets,
+                           base::ListValue* out) {
+  if (targets->empty())
+    return;
+
+  if (!ApplyTestonlyFilter(targets))
+    return;
+  if (!ApplyTypeFilter(targets))
+    return;
+
+  TargetPrintingMode printing_mode = TARGET_PRINT_LABEL;
+  if (targets->empty() || !GetTargetPrintingMode(&printing_mode))
+    return;
+  switch (printing_mode) {
+    case TARGET_PRINT_BUILDFILE:
+      PrintTargetsAsBuildfiles(*targets, out);
+      break;
+    case TARGET_PRINT_LABEL:
+      PrintTargetsAsLabels(*targets, out);
+      break;
+    case TARGET_PRINT_OUTPUT:
+      PrintTargetsAsOutputs(*targets, out);
+      break;
+  }
+}
+
+void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets) {
+  base::ListValue tmp;
+  FilterAndPrintTargets(targets, &tmp);
+  for (const auto& value : tmp) {
+    std::string string;
+    value.GetAsString(&string);
+    if (indent)
+      OutputString("  ");
+    OutputString(string);
+    OutputString("\n");
+  }
+}
+
+void FilterAndPrintTargetSet(bool indent,
+                             const std::set<const Target*>& targets) {
+  std::vector<const Target*> target_vector(targets.begin(), targets.end());
+  FilterAndPrintTargets(indent, &target_vector);
+}
+
+void FilterAndPrintTargetSet(const std::set<const Target*>& targets,
+                             base::ListValue* out) {
+  std::vector<const Target*> target_vector(targets.begin(), targets.end());
+  FilterAndPrintTargets(&target_vector, out);
+}
+
+void GetTargetsContainingFile(Setup* setup,
+                              const std::vector<const Target*>& all_targets,
+                              const SourceFile& file,
+                              bool default_toolchain_only,
+                              std::vector<TargetContainingFile>* matches) {
+  Label default_toolchain = setup->loader()->default_toolchain_label();
+  for (auto* target : all_targets) {
+    if (default_toolchain_only) {
+      // Only check targets in the default toolchain.
+      if (target->label().GetToolchainLabel() != default_toolchain)
+        continue;
+    }
+    if (auto how = TargetContainsFile(target, file))
+      matches->emplace_back(target, *how);
+  }
+}
+
+}  // namespace commands
diff --git a/src/gn/commands.h b/src/gn/commands.h
new file mode 100644 (file)
index 0000000..54a1a9d
--- /dev/null
@@ -0,0 +1,247 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_COMMANDS_H_
+#define TOOLS_GN_COMMANDS_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/values.h"
+#include "gn/target.h"
+#include "gn/unique_vector.h"
+
+class BuildSettings;
+class Config;
+class LabelPattern;
+class Setup;
+class SourceFile;
+class Target;
+class Toolchain;
+
+// Each "Run" command returns the value we should return from main().
+
+namespace commands {
+
+using CommandRunner = int (*)(const std::vector<std::string>&);
+
+extern const char kAnalyze[];
+extern const char kAnalyze_HelpShort[];
+extern const char kAnalyze_Help[];
+int RunAnalyze(const std::vector<std::string>& args);
+
+extern const char kArgs[];
+extern const char kArgs_HelpShort[];
+extern const char kArgs_Help[];
+int RunArgs(const std::vector<std::string>& args);
+
+extern const char kCheck[];
+extern const char kCheck_HelpShort[];
+extern const char kCheck_Help[];
+int RunCheck(const std::vector<std::string>& args);
+
+extern const char kClean[];
+extern const char kClean_HelpShort[];
+extern const char kClean_Help[];
+int RunClean(const std::vector<std::string>& args);
+
+extern const char kDesc[];
+extern const char kDesc_HelpShort[];
+extern const char kDesc_Help[];
+int RunDesc(const std::vector<std::string>& args);
+
+extern const char kGen[];
+extern const char kGen_HelpShort[];
+extern const char kGen_Help[];
+int RunGen(const std::vector<std::string>& args);
+
+extern const char kFormat[];
+extern const char kFormat_HelpShort[];
+extern const char kFormat_Help[];
+int RunFormat(const std::vector<std::string>& args);
+
+extern const char kHelp[];
+extern const char kHelp_HelpShort[];
+extern const char kHelp_Help[];
+int RunHelp(const std::vector<std::string>& args);
+
+extern const char kMeta[];
+extern const char kMeta_HelpShort[];
+extern const char kMeta_Help[];
+int RunMeta(const std::vector<std::string>& args);
+
+extern const char kLs[];
+extern const char kLs_HelpShort[];
+extern const char kLs_Help[];
+int RunLs(const std::vector<std::string>& args);
+
+extern const char kOutputs[];
+extern const char kOutputs_HelpShort[];
+extern const char kOutputs_Help[];
+int RunOutputs(const std::vector<std::string>& args);
+
+extern const char kPath[];
+extern const char kPath_HelpShort[];
+extern const char kPath_Help[];
+int RunPath(const std::vector<std::string>& args);
+
+extern const char kRefs[];
+extern const char kRefs_HelpShort[];
+extern const char kRefs_Help[];
+int RunRefs(const std::vector<std::string>& args);
+
+extern const char kCleanStale[];
+extern const char kCleanStale_HelpShort[];
+extern const char kCleanStale_Help[];
+int RunCleanStale(const std::vector<std::string>& args);
+
+// -----------------------------------------------------------------------------
+
+struct CommandInfo {
+  CommandInfo();
+  CommandInfo(const char* in_help_short,
+              const char* in_help,
+              CommandRunner in_runner);
+
+  const char* help_short;
+  const char* help;
+  CommandRunner runner;
+};
+
+using CommandInfoMap = std::map<std::string_view, CommandInfo>;
+
+const CommandInfoMap& GetCommands();
+
+// Helper functions for some commands ------------------------------------------
+
+// Given a setup that has already been run and some command-line input,
+// resolves that input as a target label and returns the corresponding target.
+// On failure, returns null and prints the error to the standard output.
+const Target* ResolveTargetFromCommandLineString(
+    Setup* setup,
+    const std::string& label_string);
+
+// Resolves a vector of command line inputs and figures out the full set of
+// things they resolve to.
+//
+// On success, returns true and populates the vectors. On failure, prints the
+// error and returns false.
+//
+// Patterns with wildcards will only match targets. The file_matches aren't
+// validated that they are real files or referenced by any targets. They're just
+// the set of things that didn't match anything else.
+bool ResolveFromCommandLineInput(
+    Setup* setup,
+    const std::vector<std::string>& input,
+    bool default_toolchain_only,
+    UniqueVector<const Target*>* target_matches,
+    UniqueVector<const Config*>* config_matches,
+    UniqueVector<const Toolchain*>* toolchain_matches,
+    UniqueVector<SourceFile>* file_matches);
+
+// Runs the header checker. All targets in the build should be given in
+// all_targets, and the specific targets to check should be in to_check.
+//
+// force_check, if true, will override targets opting out of header checking
+// with "check_includes = false" and will check them anyway.
+//
+// Generated files are normally not checked since they do not exist
+// unless a build has been run, but passing true for |check_generated|
+// will attempt to check them anyway, assuming they exist.
+//
+// On success, returns true. If the check fails, the error(s) will be printed
+// to stdout and false will be returned.
+bool CheckPublicHeaders(const BuildSettings* build_settings,
+                        const std::vector<const Target*>& all_targets,
+                        const std::vector<const Target*>& to_check,
+                        bool force_check,
+                        bool check_generated,
+                        bool check_system);
+
+// Filters the given list of targets by the given pattern list.
+void FilterTargetsByPatterns(const std::vector<const Target*>& input,
+                             const std::vector<LabelPattern>& filter,
+                             std::vector<const Target*>* output);
+void FilterTargetsByPatterns(const std::vector<const Target*>& input,
+                             const std::vector<LabelPattern>& filter,
+                             UniqueVector<const Target*>* output);
+
+// Removes targets from the input that match the given pattern list.
+void FilterOutTargetsByPatterns(const std::vector<const Target*>& input,
+                                const std::vector<LabelPattern>& filter,
+                                std::vector<const Target*>* output);
+
+// Builds a list of pattern from a semicolon-separated list of labels.
+bool FilterPatternsFromString(const BuildSettings* build_settings,
+                              const std::string& label_list_string,
+                              std::vector<LabelPattern>* filters,
+                              Err* err);
+
+// These are the documentation strings for the command-line flags used by
+// FilterAndPrintTargets. Commands that call that function should incorporate
+// these into their help.
+#define TARGET_PRINTING_MODE_COMMAND_LINE_HELP                                \
+  "  --as=(buildfile|label|output)\n"                                         \
+  "      How to print targets.\n"                                             \
+  "\n"                                                                        \
+  "      buildfile\n"                                                         \
+  "          Prints the build files where the given target was declared as\n" \
+  "          file names.\n"                                                   \
+  "      label  (default)\n"                                                  \
+  "          Prints the label of the target.\n"                               \
+  "      output\n"                                                            \
+  "          Prints the first output file for the target relative to the\n"   \
+  "          root build directory.\n"
+#define TARGET_TYPE_FILTER_COMMAND_LINE_HELP                                 \
+  "  --type=(action|copy|executable|group|loadable_module|shared_library|\n" \
+  "          source_set|static_library)\n"                                   \
+  "      Restrict outputs to targets matching the given type. If\n"          \
+  "      unspecified, no filtering will be performed.\n"
+#define TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP                           \
+  "  --testonly=(true|false)\n"                                            \
+  "      Restrict outputs to targets with the testonly flag set\n"         \
+  "      accordingly. When unspecified, the target's testonly flags are\n" \
+  "      ignored.\n"
+
+// Applies any testonly and type filters specified on the command line,
+// and prints the targets as specified by the --as command line flag.
+//
+// If indent is true, the results will be indented two spaces.
+//
+// The vector will be modified so that only the printed targets will remain.
+void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets);
+void FilterAndPrintTargets(std::vector<const Target*>* targets,
+                           base::ListValue* out);
+
+void FilterAndPrintTargetSet(bool indent,
+                             const std::set<const Target*>& targets);
+void FilterAndPrintTargetSet(const std::set<const Target*>& targets,
+                             base::ListValue* out);
+
+// Computes which targets reference the given file and also stores how the
+// target references the file.
+enum class HowTargetContainsFile {
+  kSources,
+  kPublic,
+  kInputs,
+  kData,
+  kScript,
+  kOutput,
+};
+using TargetContainingFile = std::pair<const Target*, HowTargetContainsFile>;
+void GetTargetsContainingFile(Setup* setup,
+                              const std::vector<const Target*>& all_targets,
+                              const SourceFile& file,
+                              bool default_toolchain_only,
+                              std::vector<TargetContainingFile>* matches);
+
+// Extra help from command_check.cc
+extern const char kNoGnCheck_Help[];
+
+}  // namespace commands
+
+#endif  // TOOLS_GN_COMMANDS_H_
diff --git a/src/gn/commands_unittest.cc b/src/gn/commands_unittest.cc
new file mode 100644 (file)
index 0000000..6d861cd
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/commands.h"
+#include "gn/label_pattern.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(Commands, FilterOutMatch) {
+  TestWithScope setup;
+  SourceDir current_dir("//");
+
+  Target target_afoo(setup.settings(), Label(SourceDir("//a/"), "foo"));
+  Target target_cbar(setup.settings(), Label(SourceDir("//c/"), "bar"));
+  std::vector<const Target*> targets{&target_afoo, &target_cbar};
+
+  Err err;
+  LabelPattern pattern_a = LabelPattern::GetPattern(
+      current_dir, std::string_view(), Value(nullptr, "//a:*"), &err);
+  EXPECT_FALSE(err.has_error());
+  LabelPattern pattern_ef = LabelPattern::GetPattern(
+      current_dir, std::string_view(), Value(nullptr, "//e:f"), &err);
+  EXPECT_FALSE(err.has_error());
+  std::vector<LabelPattern> label_patterns{pattern_a, pattern_ef};
+
+  std::vector<const Target*> output;
+  commands::FilterOutTargetsByPatterns(targets, label_patterns, &output);
+
+  EXPECT_EQ(1, output.size());
+  EXPECT_EQ(&target_cbar, output[0]);
+}
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
new file mode 100644 (file)
index 0000000..9f8ef63
--- /dev/null
@@ -0,0 +1,365 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/compile_commands_writer.h"
+
+#include <sstream>
+
+#include "base/json/string_escape.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "gn/builder.h"
+#include "gn/c_substitution_type.h"
+#include "gn/c_tool.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/path_output.h"
+#include "gn/string_output_buffer.h"
+#include "gn/substitution_writer.h"
+
+// Structure of JSON output file
+// [
+//   {
+//      "directory": "The build directory."
+//      "file": "The main source file processed by this compilation step.
+//               Must be absolute or relative to the above build directory."
+//      "command": "The compile command executed."
+//   }
+//   ...
+// ]
+
+namespace {
+
+#if defined(OS_WIN)
+const char kPrettyPrintLineEnding[] = "\r\n";
+#else
+const char kPrettyPrintLineEnding[] = "\n";
+#endif
+
+struct CompileFlags {
+  std::string includes;
+  std::string defines;
+  std::string cflags;
+  std::string cflags_c;
+  std::string cflags_cc;
+  std::string cflags_objc;
+  std::string cflags_objcc;
+  std::string framework_dirs;
+  std::string frameworks;
+};
+
+// Helper template function to call RecursiveTargetConfigToStream<std::string>
+// and return the JSON-escaped resulting string.
+//
+// NOTE: The Windows compiler cannot properly deduce the first parameter type
+// so pass it at each call site to ensure proper builds for this platform.
+template <typename T, typename Writer>
+std::string FlagsGetter(const Target* target,
+                        const std::vector<T>& (ConfigValues::*getter)() const,
+                        const Writer& writer) {
+  std::string result;
+  std::ostringstream out;
+  RecursiveTargetConfigToStream<T>(target, getter, writer, out);
+  base::EscapeJSONString(out.str(), false, &result);
+  return result;
+};
+
+void SetupCompileFlags(const Target* target,
+                       PathOutput& path_output,
+                       EscapeOptions opts,
+                       CompileFlags& flags) {
+  bool has_precompiled_headers =
+      target->config_values().has_precompiled_headers();
+
+  flags.defines =
+      FlagsGetter<std::string>(target, &ConfigValues::defines,
+                               DefineWriter(ESCAPE_COMPILATION_DATABASE));
+
+  flags.framework_dirs =
+      FlagsGetter<SourceDir>(target, &ConfigValues::framework_dirs,
+                             FrameworkDirsWriter(path_output, "-F"));
+
+  flags.frameworks = FlagsGetter<std::string>(
+      target, &ConfigValues::frameworks,
+      FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-framework"));
+  flags.frameworks += FlagsGetter<std::string>(
+      target, &ConfigValues::weak_frameworks,
+      FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-weak_framework"));
+
+  flags.includes = FlagsGetter<SourceDir>(target, &ConfigValues::include_dirs,
+                                          IncludeWriter(path_output));
+
+  // Helper lambda to call WriteOneFlag() and return the resulting
+  // escaped JSON string.
+  auto one_flag = [&](const Substitution* substitution,
+                      bool has_precompiled_headers, const char* tool_name,
+                      const std::vector<std::string>& (ConfigValues::*getter)()
+                          const) -> std::string {
+    std::string result;
+    std::ostringstream out;
+    WriteOneFlag(target, substitution, has_precompiled_headers, tool_name,
+                 getter, opts, path_output, out, /*write_substitution=*/false);
+    base::EscapeJSONString(out.str(), false, &result);
+    return result;
+  };
+
+  flags.cflags = one_flag(&CSubstitutionCFlags, false, Tool::kToolNone,
+                          &ConfigValues::cflags);
+
+  flags.cflags_c = one_flag(&CSubstitutionCFlagsC, has_precompiled_headers,
+                            CTool::kCToolCc, &ConfigValues::cflags_c);
+
+  flags.cflags_cc = one_flag(&CSubstitutionCFlagsCc, has_precompiled_headers,
+                             CTool::kCToolCxx, &ConfigValues::cflags_cc);
+
+  flags.cflags_objc =
+      one_flag(&CSubstitutionCFlagsObjC, has_precompiled_headers,
+               CTool::kCToolObjC, &ConfigValues::cflags_objc);
+
+  flags.cflags_objcc =
+      one_flag(&CSubstitutionCFlagsObjCc, has_precompiled_headers,
+               CTool::kCToolObjCxx, &ConfigValues::cflags_objcc);
+}
+
+void WriteFile(const SourceFile& source,
+               PathOutput& path_output,
+               std::ostream& out) {
+  std::ostringstream rel_source_path;
+  out << "    \"file\": \"";
+  path_output.WriteFile(out, source);
+}
+
+void WriteDirectory(std::string build_dir, std::ostream& out) {
+  out << "\",";
+  out << kPrettyPrintLineEnding;
+  out << "    \"directory\": \"";
+  out << build_dir;
+  out << "\",";
+}
+
+void WriteCommand(const Target* target,
+                  const SourceFile& source,
+                  const CompileFlags& flags,
+                  std::vector<OutputFile>& tool_outputs,
+                  PathOutput& path_output,
+                  SourceFile::Type source_type,
+                  const char* tool_name,
+                  EscapeOptions opts,
+                  std::ostream& out) {
+  EscapeOptions no_quoting(opts);
+  no_quoting.inhibit_quoting = true;
+  const Tool* tool = target->toolchain()->GetTool(tool_name);
+
+  out << kPrettyPrintLineEnding;
+  out << "    \"command\": \"";
+
+  for (const auto& range : tool->command().ranges()) {
+    // TODO: this is emitting a bonus space prior to each substitution.
+    if (range.type == &SubstitutionLiteral) {
+      EscapeJSONStringToStream(out, range.literal, no_quoting);
+    } else if (range.type == &SubstitutionOutput) {
+      path_output.WriteFiles(out, tool_outputs);
+    } else if (range.type == &CSubstitutionDefines) {
+      out << flags.defines;
+    } else if (range.type == &CSubstitutionFrameworkDirs) {
+      out << flags.framework_dirs;
+    } else if (range.type == &CSubstitutionFrameworks) {
+      out << flags.frameworks;
+    } else if (range.type == &CSubstitutionIncludeDirs) {
+      out << flags.includes;
+    } else if (range.type == &CSubstitutionCFlags) {
+      out << flags.cflags;
+    } else if (range.type == &CSubstitutionCFlagsC) {
+      if (source_type == SourceFile::SOURCE_C)
+        out << flags.cflags_c;
+    } else if (range.type == &CSubstitutionCFlagsCc) {
+      if (source_type == SourceFile::SOURCE_CPP)
+        out << flags.cflags_cc;
+    } else if (range.type == &CSubstitutionCFlagsObjC) {
+      if (source_type == SourceFile::SOURCE_M)
+        out << flags.cflags_objc;
+    } else if (range.type == &CSubstitutionCFlagsObjCc) {
+      if (source_type == SourceFile::SOURCE_MM)
+        out << flags.cflags_objcc;
+    } else if (range.type == &SubstitutionLabel ||
+               range.type == &SubstitutionLabelName ||
+               range.type == &SubstitutionLabelNoToolchain ||
+               range.type == &SubstitutionRootGenDir ||
+               range.type == &SubstitutionRootOutDir ||
+               range.type == &SubstitutionTargetGenDir ||
+               range.type == &SubstitutionTargetOutDir ||
+               range.type == &SubstitutionTargetOutputName ||
+               range.type == &SubstitutionSource ||
+               range.type == &SubstitutionSourceNamePart ||
+               range.type == &SubstitutionSourceFilePart ||
+               range.type == &SubstitutionSourceDir ||
+               range.type == &SubstitutionSourceRootRelativeDir ||
+               range.type == &SubstitutionSourceGenDir ||
+               range.type == &SubstitutionSourceOutDir ||
+               range.type == &SubstitutionSourceTargetRelative) {
+      EscapeStringToStream(out,
+                           SubstitutionWriter::GetCompilerSubstitution(
+                               target, source, range.type),
+                           opts);
+    } else {
+      // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++
+      // source files.
+      NOTREACHED() << "Unsupported substitution for this type of target : "
+                   << range.type->name;
+      continue;
+    }
+  }
+}
+
+void OutputJSON(const BuildSettings* build_settings,
+                std::vector<const Target*>& all_targets,
+                std::ostream& out) {
+  out << '[';
+  out << kPrettyPrintLineEnding;
+  bool first = true;
+  auto build_dir = build_settings->GetFullPath(build_settings->build_dir())
+                       .StripTrailingSeparators();
+  std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop.
+
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+
+  for (const auto* target : all_targets) {
+    if (!target->IsBinary())
+      continue;
+
+    // Precompute values that are the same for all sources in a target to avoid
+    // computing for every source.
+
+    PathOutput path_output(
+        target->settings()->build_settings()->build_dir(),
+        target->settings()->build_settings()->root_path_utf8(),
+        ESCAPE_NINJA_COMMAND);
+
+    CompileFlags flags;
+    SetupCompileFlags(target, path_output, opts, flags);
+
+    for (const auto& source : target->sources()) {
+      // If this source is not a C/C++/ObjC/ObjC++ source (not header) file,
+      // continue as it does not belong in the compilation database.
+      SourceFile::Type source_type = source.type();
+      if (source_type != SourceFile::SOURCE_CPP &&
+          source_type != SourceFile::SOURCE_C &&
+          source_type != SourceFile::SOURCE_M &&
+          source_type != SourceFile::SOURCE_MM)
+        continue;
+
+      const char* tool_name = Tool::kToolNone;
+      if (!target->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
+        continue;
+
+      if (!first) {
+        out << ',';
+        out << kPrettyPrintLineEnding;
+      }
+      first = false;
+      out << "  {";
+      out << kPrettyPrintLineEnding;
+
+      WriteFile(source, path_output, out);
+      WriteDirectory(base::StringPrintf("%" PRIsFP, PATH_CSTR(build_dir)), out);
+      WriteCommand(target, source, flags, tool_outputs, path_output,
+                   source_type, tool_name, opts, out);
+      out << "\"";
+      out << kPrettyPrintLineEnding;
+      out << "  }";
+    }
+  }
+
+  out << kPrettyPrintLineEnding;
+  out << "]";
+  out << kPrettyPrintLineEnding;
+}
+
+}  // namespace
+
+std::string CompileCommandsWriter::RenderJSON(
+    const BuildSettings* build_settings,
+    std::vector<const Target*>& all_targets) {
+  StringOutputBuffer json;
+  std::ostream out(&json);
+  OutputJSON(build_settings, all_targets, out);
+  return json.str();
+}
+
+bool CompileCommandsWriter::RunAndWriteFiles(
+    const BuildSettings* build_settings,
+    const Builder& builder,
+    const std::string& file_name,
+    const std::string& target_filters,
+    bool quiet,
+    Err* err) {
+  SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
+      Value(nullptr, file_name), err);
+  if (output_file.is_null())
+    return false;
+
+  base::FilePath output_path = build_settings->GetFullPath(output_file);
+
+  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+
+  std::set<std::string> target_filters_set;
+  for (auto& target :
+       base::SplitString(target_filters, ",", base::TRIM_WHITESPACE,
+                         base::SPLIT_WANT_NONEMPTY)) {
+    target_filters_set.insert(target);
+  }
+
+  StringOutputBuffer json;
+  std::ostream output_to_json(&json);
+  if (target_filters_set.empty()) {
+    OutputJSON(build_settings, all_targets, output_to_json);
+  } else {
+    std::vector<const Target*> preserved_targets =
+        FilterTargets(all_targets, target_filters_set);
+    OutputJSON(build_settings, preserved_targets, output_to_json);
+  }
+
+  if (!json.ContentsEqual(output_path)) {
+    if (!json.WriteToFile(output_path, err))
+      return false;
+  }
+  return true;
+}
+
+std::vector<const Target*> CompileCommandsWriter::FilterTargets(
+    const std::vector<const Target*>& all_targets,
+    const std::set<std::string>& target_filters_set) {
+  std::vector<const Target*> preserved_targets;
+
+  std::set<const Target*> visited;
+  for (auto& target : all_targets) {
+    if (target_filters_set.count(target->label().name())) {
+      VisitDeps(target, &visited);
+    }
+  }
+
+  preserved_targets.reserve(visited.size());
+  // Preserve the original ordering of all_targets
+  // to allow easier debugging and testing.
+  for (auto& target : all_targets) {
+    if (visited.count(target)) {
+      preserved_targets.push_back(target);
+    }
+  }
+  return preserved_targets;
+}
+
+void CompileCommandsWriter::VisitDeps(const Target* target,
+                                      std::set<const Target*>* visited) {
+  if (!visited->count(target)) {
+    visited->insert(target);
+    for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
+      VisitDeps(pair.ptr, visited);
+    }
+  }
+}
diff --git a/src/gn/compile_commands_writer.h b/src/gn/compile_commands_writer.h
new file mode 100644 (file)
index 0000000..adc2986
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
+#define TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
+
+#include "gn/err.h"
+#include "gn/target.h"
+
+class Builder;
+class BuildSettings;
+
+class CompileCommandsWriter {
+ public:
+  // Write compile commands into a json file located by parameter file_name.
+  //
+  // Parameter target_filters should be in "target_name1,target_name2..."
+  // format. If it is not empty, only targets that are reachable from targets
+  // in target_filters are used to generate compile commands.
+  //
+  // Parameter quiet is not used.
+  static bool RunAndWriteFiles(const BuildSettings* build_setting,
+                               const Builder& builder,
+                               const std::string& file_name,
+                               const std::string& target_filters,
+                               bool quiet,
+                               Err* err);
+
+  static std::string RenderJSON(const BuildSettings* build_settings,
+                                std::vector<const Target*>& all_targets);
+
+  static std::vector<const Target*> FilterTargets(
+      const std::vector<const Target*>& all_targets,
+      const std::set<std::string>& target_filters_set);
+
+ private:
+  // This function visits the deps graph of a target in a DFS fashion.
+  static void VisitDeps(const Target* target, std::set<const Target*>* visited);
+};
+
+#endif  // TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
diff --git a/src/gn/compile_commands_writer_unittest.cc b/src/gn/compile_commands_writer_unittest.cc
new file mode 100644 (file)
index 0000000..29e6a57
--- /dev/null
@@ -0,0 +1,640 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/compile_commands_writer.h"
+
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "gn/config.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+// InputConversion needs a global scheduler object.
+class CompileCommandsTest : public TestWithScheduler {
+ public:
+  CompileCommandsTest() = default;
+
+  const BuildSettings* build_settings() { return setup_.build_settings(); }
+  const Settings* settings() { return setup_.settings(); }
+  const TestWithScope& setup() { return setup_; }
+  const Toolchain* toolchain() { return setup_.toolchain(); }
+
+ private:
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(CompileCommandsTest, SourceSet) {
+  Err err;
+
+  std::vector<const Target*> targets;
+  Target target(settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  // Also test object files, which should be just passed through to the
+  // dependents to link.
+  target.sources().push_back(SourceFile("//foo/input3.o"));
+  target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.SetToolchain(toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+  targets.push_back(&target);
+
+  // Source set itself.
+  {
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(expected, out);
+  }
+
+  // A shared library that depends on the source set.
+  Target shlib_target(settings(), Label(SourceDir("//foo/"), "shlib"));
+  shlib_target.sources().push_back(SourceFile("//foo/input3.cc"));
+  shlib_target.set_output_type(Target::SHARED_LIBRARY);
+  shlib_target.public_deps().push_back(LabelTargetPair(&target));
+  shlib_target.SetToolchain(toolchain());
+  ASSERT_TRUE(shlib_target.OnResolved(&err));
+  targets.push_back(&shlib_target);
+
+  {
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input3.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input3.cc     -o  "
+        "obj/foo/libshlib.input3.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input3.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input3.cc     -o  "
+        "obj/foo/libshlib.input3.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(expected, out);
+  }
+
+  // A static library that depends on the source set (should not link it).
+  Target stlib_target(settings(), Label(SourceDir("//foo/"), "stlib"));
+  stlib_target.sources().push_back(SourceFile("//foo/input4.cc"));
+  stlib_target.set_output_type(Target::STATIC_LIBRARY);
+  stlib_target.public_deps().push_back(LabelTargetPair(&target));
+  stlib_target.SetToolchain(toolchain());
+  ASSERT_TRUE(stlib_target.OnResolved(&err));
+  targets.push_back(&stlib_target);
+
+  {
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input3.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input3.cc     -o  "
+        "obj/foo/libshlib.input3.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input4.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input4.cc     -o  "
+        "obj/foo/libstlib.input4.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "obj/foo/bar.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input2.cc     -o  "
+        "obj/foo/bar.input2.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input3.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input3.cc     -o  "
+        "obj/foo/libshlib.input3.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input4.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input4.cc     -o  "
+        "obj/foo/libstlib.input4.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(expected, out);
+  }
+}
+
+TEST_F(CompileCommandsTest, EscapeDefines) {
+  Err err;
+
+  std::vector<const Target*> targets;
+  TestTarget target(setup(), "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/input.cc"));
+  target.config_values().defines().push_back("BOOL_DEF");
+  target.config_values().defines().push_back("INT_DEF=123");
+  target.config_values().defines().push_back("STR_DEF=\"ABCD 1\"");
+  target.config_values().defines().push_back("INCLUDE=<header.h>");
+  ASSERT_TRUE(target.OnResolved(&err));
+  targets.push_back(&target);
+
+  CompileCommandsWriter writer;
+  std::string out = writer.RenderJSON(build_settings(), targets);
+
+  const char expected[] =
+      "-DBOOL_DEF -DINT_DEF=123 \\\"-DSTR_DEF=\\\\\\\"ABCD 1\\\\\\\"\\\" "
+      "\\\"-DINCLUDE=\\u003Cheader.h>\\\"";
+  EXPECT_TRUE(out.find(expected) != std::string::npos);
+}
+
+TEST_F(CompileCommandsTest, WinPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  // A precompiled header toolchain.
+  Settings pch_settings(build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cxx));
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    std::vector<const Target*> targets;
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+    targets.push_back(&no_pch_target);
+
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char no_pch_expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "withpch/obj/foo/no_pch_target.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.c\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99   -o  "
+        "withpch/obj/foo/no_pch_target.input2.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char no_pch_expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "withpch/obj/foo/no_pch_target.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.c\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99   -o  "
+        "withpch/obj/foo/no_pch_target.input2.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(no_pch_expected, out);
+  }
+
+  // This target specifies PCH.
+  {
+    std::vector<const Target*> targets;
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_header("build/precompile.h");
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.cc"));
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+    targets.push_back(&pch_target);
+
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char pch_win_expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc   "
+        "/Fpwithpch/obj/foo/pch_target_cc.pch /Yubuild/precompile.h   -o  "
+        "withpch/obj/foo/pch_target.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.c\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"cc ../../foo/input2.c   "
+        "/Fpwithpch/obj/foo/pch_target_c.pch /Yubuild/precompile.h   -o  "
+        "withpch/obj/foo/pch_target.input2.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char pch_win_expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc   "
+        "/Fpwithpch/obj/foo/pch_target_cc.pch /Yubuild/precompile.h   -o  "
+        "withpch/obj/foo/pch_target.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.c\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"cc ../../foo/input2.c   "
+        "/Fpwithpch/obj/foo/pch_target_c.pch /Yubuild/precompile.h   -o  "
+        "withpch/obj/foo/pch_target.input2.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(pch_win_expected, out);
+  }
+}
+
+TEST_F(CompileCommandsTest, GCCPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  // A precompiled header toolchain.
+  Settings pch_settings(build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cxx));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    std::vector<const Target*> targets;
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+    targets.push_back(&no_pch_target);
+
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char no_pch_expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "withpch/obj/foo/no_pch_target.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.c\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99   -o  "
+        "withpch/obj/foo/no_pch_target.input2.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char no_pch_expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc     -o  "
+        "withpch/obj/foo/no_pch_target.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.c\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99   -o  "
+        "withpch/obj/foo/no_pch_target.input2.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(no_pch_expected, out);
+  }
+
+  // This target specifies PCH.
+  {
+    std::vector<const Target*> targets;
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.h"));
+    pch_target.config_values().cflags_c().push_back("-std=c99");
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+    targets.push_back(&pch_target);
+
+    CompileCommandsWriter writer;
+    std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+    const char pch_gcc_expected[] =
+        "[\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input1.cc\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"c++ ../../foo/input1.cc   -include "
+        "withpch/obj/build/pch_target.precompile.h-cc   -o  "
+        "withpch/obj/foo/pch_target.input1.o\"\r\n"
+        "  },\r\n"
+        "  {\r\n"
+        "    \"file\": \"../../foo/input2.c\",\r\n"
+        "    \"directory\": \"out/Debug\",\r\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99 -include "
+        "withpch/obj/build/pch_target.precompile.h-c   -o  "
+        "withpch/obj/foo/pch_target.input2.o\"\r\n"
+        "  }\r\n"
+        "]\r\n";
+#else
+    const char pch_gcc_expected[] =
+        "[\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input1.cc\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"c++ ../../foo/input1.cc   -include "
+        "withpch/obj/build/pch_target.precompile.h-cc   -o  "
+        "withpch/obj/foo/pch_target.input1.o\"\n"
+        "  },\n"
+        "  {\n"
+        "    \"file\": \"../../foo/input2.c\",\n"
+        "    \"directory\": \"out/Debug\",\n"
+        "    \"command\": \"cc ../../foo/input2.c   -std=c99 -include "
+        "withpch/obj/build/pch_target.precompile.h-c   -o  "
+        "withpch/obj/foo/pch_target.input2.o\"\n"
+        "  }\n"
+        "]\n";
+#endif
+    EXPECT_EQ(pch_gcc_expected, out);
+  }
+}
+
+TEST_F(CompileCommandsTest, EscapedFlags) {
+  Err err;
+
+  std::vector<const Target*> targets;
+  Target target(settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.sources().push_back(SourceFile("//foo/input1.c"));
+  target.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target.SetToolchain(toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+  targets.push_back(&target);
+
+  CompileCommandsWriter writer;
+  std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+  const char expected[] =
+      "[\r\n"
+      "  {\r\n"
+      "    \"file\": \"../../foo/input1.c\",\r\n"
+      "    \"directory\": \"out/Debug\",\r\n"
+      "    \"command\": \"cc ../../foo/input1.c   -DCONFIG=\\\"/config\\\"   "
+      "-o  obj/foo/bar.input1.o\"\r\n"
+      "  }\r\n"
+      "]\r\n";
+#else
+  const char expected[] =
+      "[\n"
+      "  {\n"
+      "    \"file\": \"../../foo/input1.c\",\n"
+      "    \"directory\": \"out/Debug\",\n"
+      "    \"command\": \"cc ../../foo/input1.c   -DCONFIG=\\\"/config\\\"   "
+      "-o  obj/foo/bar.input1.o\"\n"
+      "  }\n"
+      "]\n";
+#endif
+  EXPECT_EQ(expected, out);
+}
+
+TEST_F(CompileCommandsTest, CompDBFilter) {
+  Err err;
+
+  std::vector<const Target*> targets;
+  Target target1(settings(), Label(SourceDir("//foo/"), "bar1"));
+  target1.set_output_type(Target::SOURCE_SET);
+  target1.sources().push_back(SourceFile("//foo/input1.c"));
+  target1.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target1.SetToolchain(toolchain());
+  ASSERT_TRUE(target1.OnResolved(&err));
+  targets.push_back(&target1);
+
+  Target target2(settings(), Label(SourceDir("//foo/"), "bar2"));
+  target2.set_output_type(Target::SOURCE_SET);
+  target2.sources().push_back(SourceFile("//foo/input2.c"));
+  target2.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target2.SetToolchain(toolchain());
+  ASSERT_TRUE(target2.OnResolved(&err));
+  targets.push_back(&target2);
+
+  Target target3(settings(), Label(SourceDir("//foo/"), "bar3"));
+  target3.set_output_type(Target::SOURCE_SET);
+  target3.sources().push_back(SourceFile("//foo/input3.c"));
+  target3.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target3.SetToolchain(toolchain());
+  ASSERT_TRUE(target3.OnResolved(&err));
+  targets.push_back(&target3);
+
+  target1.private_deps().push_back(LabelTargetPair(&target2));
+  target1.private_deps().push_back(LabelTargetPair(&target3));
+
+  CompileCommandsWriter writer;
+
+  std::set<std::string> filter1;
+  std::vector<const Target*> test_results1 =
+      writer.FilterTargets(targets, filter1);
+  ASSERT_TRUE(test_results1.empty());
+
+  std::set<std::string> filter2;
+  filter2.insert(target1.label().name());
+  std::vector<const Target*> test_results2 =
+      writer.FilterTargets(targets, filter2);
+  ASSERT_EQ(test_results2, targets);
+
+  std::set<std::string> filter3;
+  filter3.insert(target2.label().name());
+  std::vector<const Target*> test_result3 =
+      writer.FilterTargets(targets, filter3);
+  std::vector<const Target*> expected_results3;
+  expected_results3.push_back(&target2);
+  ASSERT_EQ(test_result3, expected_results3);
+}
diff --git a/src/gn/config.cc b/src/gn/config.cc
new file mode 100644 (file)
index 0000000..c0a5166
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config.h"
+
+#include "gn/err.h"
+#include "gn/input_file_manager.h"
+#include "gn/scheduler.h"
+
+Config::Config(const Settings* settings,
+               const Label& label,
+               const SourceFileSet& build_dependency_files)
+    : Item(settings, label, build_dependency_files) {}
+
+Config::~Config() = default;
+
+Config* Config::AsConfig() {
+  return this;
+}
+
+const Config* Config::AsConfig() const {
+  return this;
+}
+
+bool Config::OnResolved(Err* err) {
+  DCHECK(!resolved_);
+  resolved_ = true;
+
+  if (!configs_.empty()) {
+    // Subconfigs, flatten.
+    //
+    // Implementation note for the future: Flattening these here means we
+    // lose the ability to de-dupe subconfigs. If a subconfig is listed as
+    // a separate config or a subconfig that also applies to the target, the
+    // subconfig's flags will be duplicated.
+    //
+    // If we want to be able to de-dupe these, here's one idea. As a config is
+    // resolved, inline any sub-sub configs so the configs_ vector is a flat
+    // list, much the same way that libs and lib_dirs are pushed through
+    // targets. Do the same for Target.configs_ when a target is resolved. This
+    // will naturally de-dupe and also prevents recursive config walking to
+    // compute every possible flag, although it will expand the configs list on
+    // a target nontrivially (depending on build configuration).
+    composite_values_ = own_values_;
+    for (const auto& pair : configs_)
+      composite_values_.AppendValues(pair.ptr->resolved_values());
+  }
+  return true;
+}
diff --git a/src/gn/config.h b/src/gn/config.h
new file mode 100644 (file)
index 0000000..cb208d1
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_CONFIG_H_
+#define TOOLS_GN_CONFIG_H_
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/config_values.h"
+#include "gn/item.h"
+#include "gn/label_ptr.h"
+#include "gn/unique_vector.h"
+
+// Represents a named config in the dependency graph.
+//
+// A config can list other configs. We track both the data assigned directly
+// on the config, this list of sub-configs, and (when the config is resolved)
+// the resulting values of everything merged together. The flatten step
+// means we can avoid doing a recursive config walk for every target to compute
+// flags.
+class Config : public Item {
+ public:
+  // We track the set of build files that may affect this config, please refer
+  // to Scope for how this is determined.
+  Config(const Settings* settings,
+         const Label& label,
+         const SourceFileSet& build_dependency_files = {});
+  ~Config() override;
+
+  // Item implementation.
+  Config* AsConfig() override;
+  const Config* AsConfig() const override;
+  bool OnResolved(Err* err) override;
+
+  // The values set directly on this config. This will not contain data from
+  // sub-configs.
+  ConfigValues& own_values() { return own_values_; }
+  const ConfigValues& own_values() const { return own_values_; }
+
+  // The values that represent this config and all sub-configs combined into
+  // one. This is only valid after the config is resolved (when we know the
+  // contents of the sub-configs).
+  const ConfigValues& resolved_values() const {
+    DCHECK(resolved_);
+    if (configs_.empty())  // No sub configs, just use the regular values.
+      return own_values_;
+    return composite_values_;
+  }
+
+  // List of sub-configs.
+  const UniqueVector<LabelConfigPair>& configs() const { return configs_; }
+  UniqueVector<LabelConfigPair>& configs() { return configs_; }
+
+ private:
+  ConfigValues own_values_;
+
+  // Contains the own_values combined with sub-configs. Most configs don't have
+  // sub-configs. So as an optimization, this is not populated if there are no
+  // items in configs_. The resolved_values() getter handles this.
+  bool resolved_ = false;
+  ConfigValues composite_values_;
+
+  UniqueVector<LabelConfigPair> configs_;
+
+  DISALLOW_COPY_AND_ASSIGN(Config);
+};
+
+#endif  // TOOLS_GN_CONFIG_H_
diff --git a/src/gn/config_unittest.cc b/src/gn/config_unittest.cc
new file mode 100644 (file)
index 0000000..ec7ef3c
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+// Tests that the "resolved" values are the same as "own" values when there
+// are no subconfigs.
+TEST(Config, ResolvedNoSub) {
+  TestWithScope setup;
+  Err err;
+
+  Config config(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  config.own_values().defines().push_back("FOO");
+  ASSERT_TRUE(config.OnResolved(&err));
+
+  // The resolved values should be the same as the value we put in to
+  // own_values().
+  ASSERT_EQ(1u, config.resolved_values().defines().size());
+  EXPECT_EQ("FOO", config.resolved_values().defines()[0]);
+
+  // As an optimization, the string should actually refer to the original. This
+  // isn't required to pass for semantic correctness, though.
+  EXPECT_TRUE(&config.own_values() == &config.resolved_values());
+}
+
+// Tests that subconfigs are resolved in the correct order.
+TEST(Config, ResolvedSub) {
+  TestWithScope setup;
+  Err err;
+
+  Config sub1(setup.settings(), Label(SourceDir("//foo/"), "1"));
+  sub1.own_values().defines().push_back("ONE");
+  ASSERT_TRUE(sub1.OnResolved(&err));
+
+  Config sub2(setup.settings(), Label(SourceDir("//foo/"), "2"));
+  sub2.own_values().defines().push_back("TWO");
+  ASSERT_TRUE(sub2.OnResolved(&err));
+
+  Config config(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  config.own_values().defines().push_back("FOO");
+  config.configs().push_back(LabelConfigPair(&sub1));
+  config.configs().push_back(LabelConfigPair(&sub2));
+  ASSERT_TRUE(config.OnResolved(&err));
+
+  // The resolved values should be the same as the value we put in to
+  // own_values().
+  ASSERT_EQ(3u, config.resolved_values().defines().size());
+  EXPECT_EQ("FOO", config.resolved_values().defines()[0]);
+  EXPECT_EQ("ONE", config.resolved_values().defines()[1]);
+  EXPECT_EQ("TWO", config.resolved_values().defines()[2]);
+
+  // The "own" values should be unchanged.
+  ASSERT_EQ(1u, config.own_values().defines().size());
+  EXPECT_EQ("FOO", config.own_values().defines()[0]);
+}
+
+// Tests that subconfigs of subconfigs are resolved properly.
+TEST(Config, SubSub) {
+  TestWithScope setup;
+  Err err;
+
+  // Set up first -> middle -> last configs.
+  Config last(setup.settings(), Label(SourceDir("//foo/"), "last"));
+  last.own_values().defines().push_back("LAST");
+  ASSERT_TRUE(last.OnResolved(&err));
+
+  Config middle(setup.settings(), Label(SourceDir("//foo/"), "middle"));
+  middle.own_values().defines().push_back("MIDDLE");
+  middle.configs().push_back(LabelConfigPair(&last));
+  ASSERT_TRUE(middle.OnResolved(&err));
+
+  Config first(setup.settings(), Label(SourceDir("//foo/"), "first"));
+  first.own_values().defines().push_back("FIRST");
+  first.configs().push_back(LabelConfigPair(&middle));
+  ASSERT_TRUE(first.OnResolved(&err));
+
+  // Check final resolved defines on "first".
+  ASSERT_EQ(3u, first.resolved_values().defines().size());
+  EXPECT_EQ("FIRST", first.resolved_values().defines()[0]);
+  EXPECT_EQ("MIDDLE", first.resolved_values().defines()[1]);
+  EXPECT_EQ("LAST", first.resolved_values().defines()[2]);
+}
diff --git a/src/gn/config_values.cc b/src/gn/config_values.cc
new file mode 100644 (file)
index 0000000..9bddf99
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config_values.h"
+
+namespace {
+
+template <typename T>
+void VectorAppend(std::vector<T>* append_to,
+                  const std::vector<T>& append_this) {
+  if (append_this.empty())
+    return;
+  append_to->insert(append_to->end(), append_this.begin(), append_this.end());
+}
+
+}  // namespace
+
+ConfigValues::ConfigValues() = default;
+
+ConfigValues::~ConfigValues() = default;
+
+void ConfigValues::AppendValues(const ConfigValues& append) {
+  VectorAppend(&asmflags_, append.asmflags_);
+  VectorAppend(&arflags_, append.arflags_);
+  VectorAppend(&cflags_, append.cflags_);
+  VectorAppend(&cflags_c_, append.cflags_c_);
+  VectorAppend(&cflags_cc_, append.cflags_cc_);
+  VectorAppend(&cflags_objc_, append.cflags_objc_);
+  VectorAppend(&cflags_objcc_, append.cflags_objcc_);
+  VectorAppend(&defines_, append.defines_);
+  VectorAppend(&frameworks_, append.frameworks_);
+  VectorAppend(&weak_frameworks_, append.weak_frameworks_);
+  VectorAppend(&framework_dirs_, append.framework_dirs_);
+  VectorAppend(&include_dirs_, append.include_dirs_);
+  VectorAppend(&inputs_, append.inputs_);
+  VectorAppend(&ldflags_, append.ldflags_);
+  VectorAppend(&lib_dirs_, append.lib_dirs_);
+  VectorAppend(&libs_, append.libs_);
+  VectorAppend(&rustflags_, append.rustflags_);
+  VectorAppend(&rustenv_, append.rustenv_);
+  VectorAppend(&swiftflags_, append.swiftflags_);
+
+  // Only append precompiled header if there isn't one. It might be nice to
+  // throw an error if there are conflicting precompiled headers, but that
+  // requires piping through some context of the actual configs involved, and
+  // conflicts here should be very unusual. Instead, use the first value.
+  if (!append.precompiled_header_.empty() && !precompiled_header_.empty())
+    precompiled_header_ = append.precompiled_header_;
+  if (!append.precompiled_source_.is_null() && !precompiled_source_.is_null())
+    precompiled_source_ = append.precompiled_source_;
+}
diff --git a/src/gn/config_values.h b/src/gn/config_values.h
new file mode 100644 (file)
index 0000000..6361e78
--- /dev/null
@@ -0,0 +1,108 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_CONFIG_VALUES_H_
+#define TOOLS_GN_CONFIG_VALUES_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/lib_file.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+
+// Holds the values (include_dirs, defines, compiler flags, etc.) for a given
+// config or target.
+class ConfigValues {
+ public:
+  ConfigValues();
+  ~ConfigValues();
+
+  // Appends the values from the given config to this one.
+  void AppendValues(const ConfigValues& append);
+
+#define STRING_VALUES_ACCESSOR(name)                               \
+  const std::vector<std::string>& name() const { return name##_; } \
+  std::vector<std::string>& name() { return name##_; }
+#define DIR_VALUES_ACCESSOR(name)                                \
+  const std::vector<SourceDir>& name() const { return name##_; } \
+  std::vector<SourceDir>& name() { return name##_; }
+
+  // =================================================================
+  // IMPORTANT: If you add a new one, be sure to update AppendValues()
+  //            and command_desc.cc.
+  // =================================================================
+  STRING_VALUES_ACCESSOR(arflags)
+  STRING_VALUES_ACCESSOR(asmflags)
+  STRING_VALUES_ACCESSOR(cflags)
+  STRING_VALUES_ACCESSOR(cflags_c)
+  STRING_VALUES_ACCESSOR(cflags_cc)
+  STRING_VALUES_ACCESSOR(cflags_objc)
+  STRING_VALUES_ACCESSOR(cflags_objcc)
+  STRING_VALUES_ACCESSOR(defines)
+  DIR_VALUES_ACCESSOR(framework_dirs)
+  STRING_VALUES_ACCESSOR(frameworks)
+  STRING_VALUES_ACCESSOR(weak_frameworks)
+  DIR_VALUES_ACCESSOR(include_dirs)
+  STRING_VALUES_ACCESSOR(ldflags)
+  DIR_VALUES_ACCESSOR(lib_dirs)
+  STRING_VALUES_ACCESSOR(rustflags)
+  STRING_VALUES_ACCESSOR(rustenv)
+  STRING_VALUES_ACCESSOR(swiftflags)
+  // =================================================================
+  // IMPORTANT: If you add a new one, be sure to update AppendValues()
+  //            and command_desc.cc.
+  // =================================================================
+
+#undef STRING_VALUES_ACCESSOR
+#undef DIR_VALUES_ACCESSOR
+
+  const std::vector<SourceFile>& inputs() const { return inputs_; }
+  std::vector<SourceFile>& inputs() { return inputs_; }
+
+  const std::vector<LibFile>& libs() const { return libs_; }
+  std::vector<LibFile>& libs() { return libs_; }
+
+  const std::vector<std::pair<std::string, LibFile>>& externs() const {
+    return externs_;
+  }
+  std::vector<std::pair<std::string, LibFile>>& externs() { return externs_; }
+
+  bool has_precompiled_headers() const {
+    return !precompiled_header_.empty() || !precompiled_source_.is_null();
+  }
+  const std::string& precompiled_header() const { return precompiled_header_; }
+  void set_precompiled_header(const std::string& f) { precompiled_header_ = f; }
+  const SourceFile& precompiled_source() const { return precompiled_source_; }
+  void set_precompiled_source(const SourceFile& f) { precompiled_source_ = f; }
+
+ private:
+  std::vector<std::string> arflags_;
+  std::vector<std::string> asmflags_;
+  std::vector<std::string> cflags_;
+  std::vector<std::string> cflags_c_;
+  std::vector<std::string> cflags_cc_;
+  std::vector<std::string> cflags_objc_;
+  std::vector<std::string> cflags_objcc_;
+  std::vector<std::string> defines_;
+  std::vector<SourceDir> include_dirs_;
+  std::vector<SourceDir> framework_dirs_;
+  std::vector<std::string> frameworks_;
+  std::vector<std::string> weak_frameworks_;
+  std::vector<SourceFile> inputs_;
+  std::vector<std::string> ldflags_;
+  std::vector<SourceDir> lib_dirs_;
+  std::vector<LibFile> libs_;
+  std::vector<std::string> rustflags_;
+  std::vector<std::string> rustenv_;
+  std::vector<std::string> swiftflags_;
+  std::vector<std::pair<std::string, LibFile>> externs_;
+  // If you add a new one, be sure to update AppendValues().
+
+  std::string precompiled_header_;
+  SourceFile precompiled_source_;
+};
+
+#endif  // TOOLS_GN_CONFIG_VALUES_H_
diff --git a/src/gn/config_values_extractors.cc b/src/gn/config_values_extractors.cc
new file mode 100644 (file)
index 0000000..409e11d
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config_values_extractors.h"
+
+#include "gn/escape.h"
+
+namespace {
+
+class EscapedStringWriter {
+ public:
+  explicit EscapedStringWriter(const EscapeOptions& escape_options)
+      : escape_options_(escape_options) {}
+
+  void operator()(const std::string& s, std::ostream& out) const {
+    out << " ";
+    EscapeStringToStream(out, s, escape_options_);
+  }
+
+ private:
+  const EscapeOptions& escape_options_;
+};
+
+}  // namespace
+
+void RecursiveTargetConfigStringsToStream(
+    const Target* target,
+    const std::vector<std::string>& (ConfigValues::*getter)() const,
+    const EscapeOptions& escape_options,
+    std::ostream& out) {
+  RecursiveTargetConfigToStream(target, getter,
+                                EscapedStringWriter(escape_options), out);
+}
diff --git a/src/gn/config_values_extractors.h b/src/gn/config_values_extractors.h
new file mode 100644 (file)
index 0000000..ae51761
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_
+#define TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_
+
+#include <stddef.h>
+
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "gn/config.h"
+#include "gn/config_values.h"
+#include "gn/target.h"
+
+struct EscapeOptions;
+
+// Provides a way to iterate through all ConfigValues applying to a given
+// target. This is more complicated than normal because the target has a list
+// of configs applying to it, and also config values on the target itself.
+//
+// This iterator allows one to iterate through all of these in a defined order
+// in one convenient loop. The order is defined to be the ConfigValues on the
+// target itself first, then the applying configs, in order.
+//
+// Example:
+//   for (ConfigValueIterator iter(target); !iter.done(); iter.Next())
+//     DoSomething(iter.cur());
+class ConfigValuesIterator {
+ public:
+  explicit ConfigValuesIterator(const Target* target) : target_(target) {}
+
+  bool done() const {
+    return cur_index_ >= static_cast<int>(target_->configs().size());
+  }
+
+  const ConfigValues& cur() const {
+    if (cur_index_ == -1)
+      return target_->config_values();
+    return target_->configs()[cur_index_].ptr->resolved_values();
+  }
+
+  // Returns the origin of who added this config, if any. This will always be
+  // null for the config values of a target itself.
+  const ParseNode* origin() const {
+    if (cur_index_ == -1)
+      return nullptr;
+    return target_->configs()[cur_index_].origin;
+  }
+
+  void Next() { cur_index_++; }
+
+  // Returns the config holding the current config values, or NULL for those
+  // config values associated with the target itself.
+  const Config* GetCurrentConfig() const {
+    if (cur_index_ == -1)
+      return nullptr;
+    return target_->configs()[cur_index_].ptr;
+  }
+
+ private:
+  const Target* target_;
+
+  // Represents an index into the target_'s configs() or, when -1, the config
+  // values on the target itself.
+  int cur_index_ = -1;
+};
+
+template <typename T, class Writer>
+inline void ConfigValuesToStream(const ConfigValues& values,
+                                 const std::vector<T>& (ConfigValues::*getter)()
+                                     const,
+                                 const Writer& writer,
+                                 std::ostream& out) {
+  const std::vector<T>& v = (values.*getter)();
+  for (size_t i = 0; i < v.size(); i++)
+    writer(v[i], out);
+}
+
+// Writes a given config value that applies to a given target. This collects
+// all values from the target itself and all configs that apply, and writes
+// then in order.
+template <typename T, class Writer>
+inline void RecursiveTargetConfigToStream(
+    const Target* target,
+    const std::vector<T>& (ConfigValues::*getter)() const,
+    const Writer& writer,
+    std::ostream& out) {
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next())
+    ConfigValuesToStream(iter.cur(), getter, writer, out);
+}
+
+// Writes the values out as strings with no transformation.
+void RecursiveTargetConfigStringsToStream(
+    const Target* target,
+    const std::vector<std::string>& (ConfigValues::*getter)() const,
+    const EscapeOptions& escape_options,
+    std::ostream& out);
+
+#endif  // TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_
diff --git a/src/gn/config_values_extractors_unittest.cc b/src/gn/config_values_extractors_unittest.cc
new file mode 100644 (file)
index 0000000..7c520ad
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "gn/config.h"
+#include "gn/config_values_extractors.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+struct FlagWriter {
+  void operator()(const std::string& dir, std::ostream& out) const {
+    out << dir << " ";
+  }
+};
+
+struct IncludeWriter {
+  void operator()(const SourceDir& dir, std::ostream& out) const {
+    out << dir.value() << " ";
+  }
+};
+
+}  // namespace
+
+TEST(ConfigValuesExtractors, IncludeOrdering) {
+  TestWithScope setup;
+  Err err;
+
+  // Construct a chain of dependencies: target -> dep1 -> dep2
+  // Add representative values: cflags (opaque, always copied) and include_dirs
+  // (uniquified) to each one so we can check what comes out the other end.
+
+  // Set up dep2, direct and all dependent configs.
+  Config dep2_all(setup.settings(), Label(SourceDir("//dep2/"), "all"));
+  dep2_all.own_values().cflags().push_back("--dep2-all");
+  dep2_all.own_values().include_dirs().push_back(SourceDir("//dep2/all/"));
+  ASSERT_TRUE(dep2_all.OnResolved(&err));
+
+  Config dep2_direct(setup.settings(), Label(SourceDir("//dep2/"), "direct"));
+  dep2_direct.own_values().cflags().push_back("--dep2-direct");
+  dep2_direct.own_values().include_dirs().push_back(
+      SourceDir("//dep2/direct/"));
+  ASSERT_TRUE(dep2_direct.OnResolved(&err));
+
+  Target dep2(setup.settings(), Label(SourceDir("//dep2/"), "dep2"));
+  dep2.set_output_type(Target::SOURCE_SET);
+  dep2.visibility().SetPublic();
+  dep2.SetToolchain(setup.toolchain());
+  dep2.all_dependent_configs().push_back(LabelConfigPair(&dep2_all));
+  dep2.public_configs().push_back(LabelConfigPair(&dep2_direct));
+
+  // Set up dep1, direct and all dependent configs. Also set up a subconfig
+  // on "dep1_all" to test sub configs.
+  Config dep1_all_sub(setup.settings(), Label(SourceDir("//dep1"), "allch"));
+  dep1_all_sub.own_values().cflags().push_back("--dep1-all-sub");
+  ASSERT_TRUE(dep1_all_sub.OnResolved(&err));
+
+  Config dep1_all(setup.settings(), Label(SourceDir("//dep1/"), "all"));
+  dep1_all.own_values().cflags().push_back("--dep1-all");
+  dep1_all.own_values().include_dirs().push_back(SourceDir("//dep1/all/"));
+  dep1_all.configs().push_back(LabelConfigPair(&dep1_all_sub));
+  ASSERT_TRUE(dep1_all.OnResolved(&err));
+
+  Config dep1_direct(setup.settings(), Label(SourceDir("//dep1/"), "direct"));
+  dep1_direct.own_values().cflags().push_back("--dep1-direct");
+  dep1_direct.own_values().include_dirs().push_back(
+      SourceDir("//dep1/direct/"));
+  ASSERT_TRUE(dep1_direct.OnResolved(&err));
+
+  Target dep1(setup.settings(), Label(SourceDir("//dep1/"), "dep1"));
+  dep1.set_output_type(Target::SOURCE_SET);
+  dep1.visibility().SetPublic();
+  dep1.SetToolchain(setup.toolchain());
+  dep1.all_dependent_configs().push_back(LabelConfigPair(&dep1_all));
+  dep1.public_configs().push_back(LabelConfigPair(&dep1_direct));
+  dep1.private_deps().push_back(LabelTargetPair(&dep2));
+
+  // Set up target, direct and all dependent configs.
+  Config target_all(setup.settings(), Label(SourceDir("//target/"), "all"));
+  target_all.visibility().SetPublic();
+  target_all.own_values().cflags().push_back("--target-all");
+  target_all.own_values().include_dirs().push_back(SourceDir("//target/all/"));
+  ASSERT_TRUE(target_all.OnResolved(&err));
+
+  Config target_direct(setup.settings(),
+                       Label(SourceDir("//target/"), "direct"));
+  target_direct.visibility().SetPublic();
+  target_direct.own_values().cflags().push_back("--target-direct");
+  target_direct.own_values().include_dirs().push_back(
+      SourceDir("//target/direct/"));
+  ASSERT_TRUE(target_direct.OnResolved(&err));
+
+  // This config is applied directly to target.
+  Config target_config(setup.settings(),
+                       Label(SourceDir("//target/"), "config"));
+  target_config.visibility().SetPublic();
+  target_config.own_values().cflags().push_back("--target-config");
+  target_config.own_values().include_dirs().push_back(
+      SourceDir("//target/config/"));
+  ASSERT_TRUE(target_config.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//target/"), "target"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.SetToolchain(setup.toolchain());
+  target.all_dependent_configs().push_back(LabelConfigPair(&target_all));
+  target.public_configs().push_back(LabelConfigPair(&target_direct));
+  target.configs().push_back(LabelConfigPair(&target_config));
+  target.private_deps().push_back(LabelTargetPair(&dep1));
+
+  // Additionally add some values directly on "target".
+  target.config_values().cflags().push_back("--target");
+  target.config_values().include_dirs().push_back(SourceDir("//target/"));
+
+  // Mark targets resolved. This should push dependent configs.
+  ASSERT_TRUE(dep2.OnResolved(&err));
+  ASSERT_TRUE(dep1.OnResolved(&err));
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Verify cflags by serializing.
+  std::ostringstream flag_out;
+  FlagWriter flag_writer;
+  RecursiveTargetConfigToStream<std::string, FlagWriter>(
+      &target, &ConfigValues::cflags, flag_writer, flag_out);
+  EXPECT_EQ(flag_out.str(),
+            "--target --target-config --target-all --target-direct "
+            "--dep1-all --dep1-all-sub --dep2-all --dep1-direct ");
+
+  // Verify include dirs by serializing.
+  std::ostringstream include_out;
+  IncludeWriter include_writer;
+  RecursiveTargetConfigToStream<SourceDir, IncludeWriter>(
+      &target, &ConfigValues::include_dirs, include_writer, include_out);
+  EXPECT_EQ(include_out.str(),
+            "//target/ //target/config/ //target/all/ //target/direct/ "
+            "//dep1/all/ //dep2/all/ //dep1/direct/ ");
+}
diff --git a/src/gn/config_values_generator.cc b/src/gn/config_values_generator.cc
new file mode 100644 (file)
index 0000000..fac03d7
--- /dev/null
@@ -0,0 +1,170 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config_values_generator.h"
+
+#include "base/strings/string_util.h"
+#include "gn/build_settings.h"
+#include "gn/config_values.h"
+#include "gn/frameworks_utils.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+namespace {
+
+void GetStringList(Scope* scope,
+                   const char* var_name,
+                   ConfigValues* config_values,
+                   std::vector<std::string>& (ConfigValues::*accessor)(),
+                   Err* err) {
+  const Value* value = scope->GetValue(var_name, true);
+  if (!value)
+    return;  // No value, empty input and succeed.
+
+  ExtractListOfStringValues(*value, &(config_values->*accessor)(), err);
+}
+
+void GetDirList(Scope* scope,
+                const char* var_name,
+                ConfigValues* config_values,
+                const SourceDir input_dir,
+                std::vector<SourceDir>& (ConfigValues::*accessor)(),
+                Err* err) {
+  const Value* value = scope->GetValue(var_name, true);
+  if (!value)
+    return;  // No value, empty input and succeed.
+
+  std::vector<SourceDir> result;
+  ExtractListOfRelativeDirs(scope->settings()->build_settings(), *value,
+                            input_dir, &result, err);
+  (config_values->*accessor)().swap(result);
+}
+
+void GetFrameworksList(Scope* scope,
+                       const char* var_name,
+                       ConfigValues* config_values,
+                       std::vector<std::string>& (ConfigValues::*accessor)(),
+                       Err* err) {
+  const Value* value = scope->GetValue(var_name, true);
+  if (!value)
+    return;
+
+  std::vector<std::string> frameworks;
+  if (!ExtractListOfStringValues(*value, &frameworks, err))
+    return;
+
+  // All strings must end with ".frameworks".
+  for (const std::string& framework : frameworks) {
+    std::string_view framework_name = GetFrameworkName(framework);
+    if (framework_name.empty()) {
+      *err = Err(*value,
+                 "This frameworks value is wrong."
+                 "All listed frameworks names must not include any\n"
+                 "path component and have \".framework\" extension.");
+      return;
+    }
+  }
+
+  (config_values->*accessor)().swap(frameworks);
+}
+
+}  // namespace
+
+ConfigValuesGenerator::ConfigValuesGenerator(ConfigValues* dest_values,
+                                             Scope* scope,
+                                             const SourceDir& input_dir,
+                                             Err* err)
+    : config_values_(dest_values),
+      scope_(scope),
+      input_dir_(input_dir),
+      err_(err) {}
+
+ConfigValuesGenerator::~ConfigValuesGenerator() = default;
+
+void ConfigValuesGenerator::Run() {
+#define FILL_STRING_CONFIG_VALUE(name) \
+  GetStringList(scope_, #name, config_values_, &ConfigValues::name, err_);
+#define FILL_DIR_CONFIG_VALUE(name)                                          \
+  GetDirList(scope_, #name, config_values_, input_dir_, &ConfigValues::name, \
+             err_);
+
+  FILL_STRING_CONFIG_VALUE(arflags)
+  FILL_STRING_CONFIG_VALUE(asmflags)
+  FILL_STRING_CONFIG_VALUE(cflags)
+  FILL_STRING_CONFIG_VALUE(cflags_c)
+  FILL_STRING_CONFIG_VALUE(cflags_cc)
+  FILL_STRING_CONFIG_VALUE(cflags_objc)
+  FILL_STRING_CONFIG_VALUE(cflags_objcc)
+  FILL_STRING_CONFIG_VALUE(defines)
+  FILL_DIR_CONFIG_VALUE(framework_dirs)
+  FILL_DIR_CONFIG_VALUE(include_dirs)
+  FILL_STRING_CONFIG_VALUE(ldflags)
+  FILL_DIR_CONFIG_VALUE(lib_dirs)
+  FILL_STRING_CONFIG_VALUE(rustflags)
+  FILL_STRING_CONFIG_VALUE(rustenv)
+  FILL_STRING_CONFIG_VALUE(swiftflags)
+
+#undef FILL_STRING_CONFIG_VALUE
+#undef FILL_DIR_CONFIG_VALUE
+
+  // Inputs
+  const Value* inputs_value = scope_->GetValue(variables::kInputs, true);
+  if (inputs_value) {
+    ExtractListOfRelativeFiles(scope_->settings()->build_settings(),
+                               *inputs_value, input_dir_,
+                               &config_values_->inputs(), err_);
+  }
+
+  // Libs
+  const Value* libs_value = scope_->GetValue(variables::kLibs, true);
+  if (libs_value) {
+    ExtractListOfLibs(scope_->settings()->build_settings(), *libs_value,
+                      input_dir_, &config_values_->libs(), err_);
+  }
+
+  // Externs
+  const Value* externs_value = scope_->GetValue(variables::kExterns, true);
+  if (externs_value) {
+    ExtractListOfExterns(scope_->settings()->build_settings(), *externs_value,
+                         input_dir_, &config_values_->externs(), err_);
+  }
+
+  // Frameworks
+  GetFrameworksList(scope_, variables::kFrameworks, config_values_,
+                    &ConfigValues::frameworks, err_);
+  GetFrameworksList(scope_, variables::kWeakFrameworks, config_values_,
+                    &ConfigValues::weak_frameworks, err_);
+
+  // Precompiled headers.
+  const Value* precompiled_header_value =
+      scope_->GetValue(variables::kPrecompiledHeader, true);
+  if (precompiled_header_value) {
+    if (!precompiled_header_value->VerifyTypeIs(Value::STRING, err_))
+      return;
+
+    // Check for common errors. This is a string and not a file.
+    const std::string& pch_string = precompiled_header_value->string_value();
+    if (base::StartsWith(pch_string, "//", base::CompareCase::SENSITIVE)) {
+      *err_ = Err(
+          *precompiled_header_value, "This precompiled_header value is wrong.",
+          "You need to specify a string that the compiler will match against\n"
+          "the #include lines rather than a GN-style file name.\n");
+      return;
+    }
+    config_values_->set_precompiled_header(pch_string);
+  }
+
+  const Value* precompiled_source_value =
+      scope_->GetValue(variables::kPrecompiledSource, true);
+  if (precompiled_source_value) {
+    config_values_->set_precompiled_source(input_dir_.ResolveRelativeFile(
+        *precompiled_source_value, err_,
+        scope_->settings()->build_settings()->root_path_utf8()));
+    if (err_->has_error())
+      return;
+  }
+}
diff --git a/src/gn/config_values_generator.h b/src/gn/config_values_generator.h
new file mode 100644 (file)
index 0000000..b7b008c
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_CONFIG_VALUES_GENERATOR_H_
+#define TOOLS_GN_CONFIG_VALUES_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/source_dir.h"
+
+class ConfigValues;
+class Err;
+class Scope;
+
+// This class fills in the config values from a given scope. It's shared
+// between the "config" function call and all the different binary target types
+// (shared library, static library, etc.) since all of these support the
+// various flags stored in the ConfigValues class.
+class ConfigValuesGenerator {
+ public:
+  ConfigValuesGenerator(ConfigValues* dest_values,
+                        Scope* scope,
+                        const SourceDir& input_dir,
+                        Err* err);
+  ~ConfigValuesGenerator();
+
+  // Sets the error passed to the constructor on failure.
+  void Run();
+
+ private:
+  ConfigValues* config_values_;
+  Scope* scope_;
+  const SourceDir input_dir_;
+  Err* err_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConfigValuesGenerator);
+};
+
+// For using in documentation for functions which use this.
+#define CONFIG_VALUES_VARS_HELP                                            \
+  "  Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,\n"     \
+  "         asmflags, defines, include_dirs, inputs, ldflags, lib_dirs,\n" \
+  "         libs, precompiled_header, precompiled_source, rustflags,\n"    \
+  "         rustenv, swiftflags\n"
+
+#endif  // TOOLS_GN_CONFIG_VALUES_GENERATOR_H_
diff --git a/src/gn/copy_target_generator.cc b/src/gn/copy_target_generator.cc
new file mode 100644 (file)
index 0000000..a0dc924
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/copy_target_generator.h"
+
+#include "gn/build_settings.h"
+#include "gn/filesystem_utils.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+
+CopyTargetGenerator::CopyTargetGenerator(Target* target,
+                                         Scope* scope,
+                                         const FunctionCallNode* function_call,
+                                         Err* err)
+    : TargetGenerator(target, scope, function_call, err) {}
+
+CopyTargetGenerator::~CopyTargetGenerator() = default;
+
+void CopyTargetGenerator::DoRun() {
+  target_->set_output_type(Target::COPY_FILES);
+
+  if (!FillSources())
+    return;
+  if (!FillOutputs(true))
+    return;
+
+  if (target_->sources().empty()) {
+    *err_ = Err(
+        function_call_, "Empty sources for copy command.",
+        "You have to specify at least one file to copy in the \"sources\".");
+    return;
+  }
+  if (target_->action_values().outputs().list().size() != 1) {
+    *err_ = Err(
+        function_call_, "Copy command must have exactly one output.",
+        "You must specify exactly one value in the \"outputs\" array for the "
+        "destination of the copy\n(see \"gn help copy\"). If there are "
+        "multiple sources to copy, use source expansion\n(see \"gn help "
+        "source_expansion\").");
+    return;
+  }
+}
diff --git a/src/gn/copy_target_generator.h b/src/gn/copy_target_generator.h
new file mode 100644 (file)
index 0000000..bec05e3
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_COPY_TARGET_GENERATOR_H_
+#define TOOLS_GN_COPY_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target_generator.h"
+
+// Populates a Target with the values from a copy rule.
+class CopyTargetGenerator : public TargetGenerator {
+ public:
+  CopyTargetGenerator(Target* target,
+                      Scope* scope,
+                      const FunctionCallNode* function_call,
+                      Err* err);
+  ~CopyTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CopyTargetGenerator);
+};
+
+#endif  // TOOLS_GN_COPY_TARGET_GENERATOR_H_
diff --git a/src/gn/create_bundle_target_generator.cc b/src/gn/create_bundle_target_generator.cc
new file mode 100644 (file)
index 0000000..b614a9c
--- /dev/null
@@ -0,0 +1,318 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/create_bundle_target_generator.h"
+
+#include <map>
+
+#include "base/logging.h"
+#include "gn/filesystem_utils.h"
+#include "gn/label_pattern.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/substitution_type.h"
+#include "gn/target.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+CreateBundleTargetGenerator::CreateBundleTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err) {}
+
+CreateBundleTargetGenerator::~CreateBundleTargetGenerator() = default;
+
+void CreateBundleTargetGenerator::DoRun() {
+  target_->set_output_type(Target::CREATE_BUNDLE);
+
+  BundleData& bundle_data = target_->bundle_data();
+  if (!FillBundleDir(SourceDir(), variables::kBundleRootDir,
+                     &bundle_data.root_dir()))
+    return;
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleContentsDir,
+                     &bundle_data.contents_dir()))
+    return;
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleResourcesDir,
+                     &bundle_data.resources_dir()))
+    return;
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleExecutableDir,
+                     &bundle_data.executable_dir()))
+    return;
+
+  if (!FillXcodeExtraAttributes())
+    return;
+
+  if (!FillProductType())
+    return;
+
+  if (!FillPartialInfoPlist())
+    return;
+
+  if (!FillXcodeTestApplicationName())
+    return;
+
+  if (!FillCodeSigningScript())
+    return;
+
+  if (!FillCodeSigningSources())
+    return;
+
+  if (!FillCodeSigningOutputs())
+    return;
+
+  if (!FillCodeSigningArgs())
+    return;
+
+  if (!FillBundleDepsFilter())
+    return;
+
+  if (!FillXcassetCompilerFlags())
+    return;
+}
+
+bool CreateBundleTargetGenerator::FillBundleDir(
+    const SourceDir& bundle_root_dir,
+    const std::string_view& name,
+    SourceDir* bundle_dir) {
+  // All bundle_foo_dir properties are optional. They are only required if they
+  // are used in an expansion. The check is performed there.
+  const Value* value = scope_->GetValue(name, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+  std::string str = value->string_value();
+  if (!str.empty() && str[str.size() - 1] != '/')
+    str.push_back('/');
+  if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(), str,
+                                 value->origin(), err_))
+    return false;
+  if (str != bundle_root_dir.value() &&
+      !IsStringInOutputDir(bundle_root_dir, str)) {
+    *err_ = Err(
+        value->origin(), "Path is not in bundle root dir.",
+        "The given file should be in the bundle root directory or below.\n"
+        "Normally you would do \"$bundle_root_dir/foo\". I interpreted this\n"
+        "as \"" +
+            str + "\".");
+    return false;
+  }
+  *bundle_dir = SourceDir(std::move(str));
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillXcodeExtraAttributes() {
+  // Need to get a mutable value to mark all values in the scope as used. This
+  // cannot be done on a const Scope.
+  Value* value = scope_->GetMutableValue(variables::kXcodeExtraAttributes,
+                                         Scope::SEARCH_CURRENT, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::SCOPE, err_))
+    return false;
+
+  Scope* scope_value = value->scope_value();
+
+  Scope::KeyValueMap value_map;
+  scope_value->GetCurrentScopeValues(&value_map);
+  scope_value->MarkAllUsed();
+
+  std::map<std::string, std::string> xcode_extra_attributes;
+  for (const auto& iter : value_map) {
+    if (!iter.second.VerifyTypeIs(Value::STRING, err_))
+      return false;
+
+    xcode_extra_attributes.insert(
+        std::make_pair(std::string(iter.first), iter.second.string_value()));
+  }
+
+  target_->bundle_data().xcode_extra_attributes() =
+      std::move(xcode_extra_attributes);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillProductType() {
+  const Value* value = scope_->GetValue(variables::kProductType, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  target_->bundle_data().product_type().assign(value->string_value());
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillPartialInfoPlist() {
+  const Value* value = scope_->GetValue(variables::kPartialInfoPlist, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  const BuildSettings* build_settings = scope_->settings()->build_settings();
+  SourceFile path = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, build_settings->root_path_utf8());
+
+  if (err_->has_error())
+    return false;
+
+  if (!EnsureStringIsInOutputDir(build_settings->build_dir(), path.value(),
+                                 value->origin(), err_))
+    return false;
+
+  target_->bundle_data().set_partial_info_plist(path);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillXcodeTestApplicationName() {
+  const Value* value =
+      scope_->GetValue(variables::kXcodeTestApplicationName, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  target_->bundle_data().xcode_test_application_name().assign(
+      value->string_value());
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningScript() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningScript, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  SourceFile script_file = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, scope_->settings()->build_settings()->root_path_utf8());
+  if (err_->has_error())
+    return false;
+
+  target_->bundle_data().set_code_signing_script(script_file);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningSources() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningSources, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_sources.");
+    return false;
+  }
+
+  Target::FileList script_sources;
+  if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+                                  scope_->GetSourceDir(), &script_sources,
+                                  err_))
+    return false;
+
+  target_->bundle_data().code_signing_sources() = std::move(script_sources);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningOutputs() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningOutputs, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_outputs.");
+    return false;
+  }
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  SubstitutionList& outputs = target_->bundle_data().code_signing_outputs();
+  if (!outputs.Parse(*value, err_))
+    return false;
+
+  if (outputs.list().empty()) {
+    *err_ =
+        Err(function_call_,
+            "Code signing script has no output."
+            "If you have no outputs, the build system can not tell when your\n"
+            "code signing script needs to be run.");
+    return false;
+  }
+
+  // Validate that outputs are in the output dir.
+  CHECK_EQ(value->list_value().size(), outputs.list().size());
+  for (size_t i = 0; i < value->list_value().size(); ++i) {
+    if (!EnsureSubstitutionIsInOutputDir(outputs.list()[i],
+                                         value->list_value()[i]))
+      return false;
+  }
+
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningArgs() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningArgs, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_args.");
+    return false;
+  }
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  return target_->bundle_data().code_signing_args().Parse(*value, err_);
+}
+
+bool CreateBundleTargetGenerator::FillBundleDepsFilter() {
+  const Value* value = scope_->GetValue(variables::kBundleDepsFilter, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  const SourceDir& current_dir = scope_->GetSourceDir();
+  std::vector<LabelPattern>& bundle_deps_filter =
+      target_->bundle_data().bundle_deps_filter();
+  for (const auto& item : value->list_value()) {
+    bundle_deps_filter.push_back(LabelPattern::GetPattern(
+        current_dir, scope_->settings()->build_settings()->root_path_utf8(),
+        item, err_));
+    if (err_->has_error())
+      return false;
+  }
+
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillXcassetCompilerFlags() {
+  const Value* value = scope_->GetValue(variables::kXcassetCompilerFlags, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  return target_->bundle_data().xcasset_compiler_flags().Parse(*value, err_);
+}
diff --git a/src/gn/create_bundle_target_generator.h b/src/gn/create_bundle_target_generator.h
new file mode 100644 (file)
index 0000000..6f13fb4
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_CREATE_BUNDLE_TARGET_GENERATOR_H_
+#define TOOLS_GN_CREATE_BUNDLE_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target_generator.h"
+
+class SourceDir;
+
+// Populates a Target with the values from a create_bundle rule.
+class CreateBundleTargetGenerator : public TargetGenerator {
+ public:
+  CreateBundleTargetGenerator(Target* target,
+                              Scope* scope,
+                              const FunctionCallNode* function_call,
+                              Err* err);
+  ~CreateBundleTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillBundleDir(const SourceDir& bundle_root_dir,
+                     const std::string_view& name,
+                     SourceDir* bundle_dir);
+
+  bool FillXcodeExtraAttributes();
+
+  bool FillProductType();
+  bool FillPartialInfoPlist();
+  bool FillXcodeTestApplicationName();
+
+  bool FillCodeSigningScript();
+  bool FillCodeSigningSources();
+  bool FillCodeSigningOutputs();
+  bool FillCodeSigningArgs();
+  bool FillBundleDepsFilter();
+  bool FillXcassetCompilerFlags();
+
+  DISALLOW_COPY_AND_ASSIGN(CreateBundleTargetGenerator);
+};
+
+#endif  // TOOLS_GN_CREATE_BUNDLE_TARGET_GENERATOR_H_
diff --git a/src/gn/deps_iterator.cc b/src/gn/deps_iterator.cc
new file mode 100644 (file)
index 0000000..33802fa
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/deps_iterator.h"
+
+#include "gn/target.h"
+
+DepsIterator::DepsIterator() : current_index_(0) {
+  vect_stack_[0] = nullptr;
+  vect_stack_[1] = nullptr;
+  vect_stack_[2] = nullptr;
+}
+
+DepsIterator::DepsIterator(const LabelTargetVector* a,
+                           const LabelTargetVector* b,
+                           const LabelTargetVector* c)
+    : current_index_(0) {
+  vect_stack_[0] = a;
+  vect_stack_[1] = b;
+  vect_stack_[2] = c;
+
+  if (vect_stack_[0] && vect_stack_[0]->empty())
+    operator++();
+}
+
+// Advance to the next position. This assumes there are more vectors.
+//
+// For internal use, this function tolerates an initial index equal to the
+// length of the current vector. In this case, it will advance to the next
+// one.
+DepsIterator& DepsIterator::operator++() {
+  DCHECK(vect_stack_[0]);
+
+  current_index_++;
+  if (current_index_ >= vect_stack_[0]->size()) {
+    // Advance to next vect. Shift the elements left by one.
+    vect_stack_[0] = vect_stack_[1];
+    vect_stack_[1] = vect_stack_[2];
+    vect_stack_[2] = nullptr;
+
+    current_index_ = 0;
+
+    if (vect_stack_[0] && vect_stack_[0]->empty())
+      operator++();
+  }
+  return *this;
+}
+
+DepsIteratorRange::DepsIteratorRange(const DepsIterator& b)
+    : begin_(b), end_() {}
+
+DepsIteratorRange::~DepsIteratorRange() = default;
diff --git a/src/gn/deps_iterator.h b/src/gn/deps_iterator.h
new file mode 100644 (file)
index 0000000..f8b25bb
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_DEPS_ITERATOR_H_
+#define TOOLS_GN_DEPS_ITERATOR_H_
+
+#include <stddef.h>
+
+#include "gn/label_ptr.h"
+
+// Provides an iterator for iterating over multiple LabelTargetVectors to
+// make it convenient to iterate over all deps of a target.
+//
+// This works by maintaining a simple stack of vectors (since we have a fixed
+// number of deps types). When the stack is empty, we've reached the end. This
+// means that the default-constructed iterator == end() for any sequence.
+class DepsIterator {
+ public:
+  // Creates an empty iterator.
+  DepsIterator();
+
+  // Iterate over the deps in the given vectors. If passing less than three,
+  // pad with nulls.
+  DepsIterator(const LabelTargetVector* a,
+               const LabelTargetVector* b,
+               const LabelTargetVector* c);
+
+  // Prefix increment operator. This assumes there are more items (i.e.
+  // *this != DepsIterator()).
+  //
+  // For internal use, this function tolerates an initial index equal to the
+  // length of the current vector. In this case, it will advance to the next
+  // one.
+  DepsIterator& operator++();
+
+  // Comparison for STL-based loops.
+  bool operator!=(const DepsIterator& other) const {
+    return current_index_ != other.current_index_ ||
+           vect_stack_[0] != other.vect_stack_[0] ||
+           vect_stack_[1] != other.vect_stack_[1] ||
+           vect_stack_[2] != other.vect_stack_[2];
+  }
+
+  // Dereference operator for STL-compatible iterators.
+  const LabelTargetPair& operator*() const {
+    DCHECK_LT(current_index_, vect_stack_[0]->size());
+    return (*vect_stack_[0])[current_index_];
+  }
+
+ private:
+  const LabelTargetVector* vect_stack_[3];
+
+  size_t current_index_;
+};
+
+// Provides a virtual container implementing begin() and end() for a
+// sequence of deps. This can then be used in range-based for loops.
+class DepsIteratorRange {
+ public:
+  explicit DepsIteratorRange(const DepsIterator& b);
+  ~DepsIteratorRange();
+
+  const DepsIterator& begin() const { return begin_; }
+  const DepsIterator& end() const { return end_; }
+
+ private:
+  DepsIterator begin_;
+  DepsIterator end_;
+};
+
+#endif  // TOOLS_GN_DEPS_ITERATOR_H_
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
new file mode 100644 (file)
index 0000000..bbbf831
--- /dev/null
@@ -0,0 +1,891 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <set>
+
+#include "base/json/json_writer.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/commands.h"
+#include "gn/config.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/desc_builder.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/runtime_deps.h"
+#include "gn/rust_variables.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/standard_out.h"
+#include "gn/substitution_writer.h"
+#include "gn/swift_variables.h"
+#include "gn/variables.h"
+
+// Example structure of Value for single target
+// (not applicable or empty fields will be omitted depending on target type)
+//
+// target_properties = {
+//   "type" : "output_type", // matching Target::GetStringForOutputType
+//   "toolchain" : "toolchain_name",
+//   "visibility" : [ list of visibility pattern descriptions ],
+//   "test_only" : true or false,
+//   "check_includes": true or false,
+//   "allow_circular_includes_from": [ list of target names ],
+//   "sources" : [ list of source files ],
+//   "public" : either "*" or [ list of public headers],
+//   "inputs" : [ list of inputs for target ],
+//   "configs" : [ list of configs for this target ],
+//   "public_configs" : [ list of public configs for this target],
+//   "all_dependent_configs", [ list of all dependent configs for this target],
+//   "script" : "script for action targets",
+//   "args" : [ argument list for action targets ],
+//   "depfile : "file name for action input dependencies",
+//   "outputs" : [ list of target outputs ],
+//   "arflags", "asmflags", "cflags", "cflags_c",
+//   "clfags_cc", "cflags_objc", "clfags_objcc" : [ list of flags],
+//   "defines" : [ list of preprocessor definitions ],
+//   "include_dirs" : [ list of include directories ],
+//   "precompiled_header" : "name of precompiled header file",
+//   "precompiled_source" : "path to precompiled source",
+//   "deps : [ list of target dependencies ],
+//   "libs" : [ list of libraries ],
+//   "lib_dirs" : [ list of library directories ]
+//   "metadata" : [ dictionary of target metadata values ]
+//   "data_keys" : [ list of target data keys ]
+//   "walk_keys" : [ list of target walk keys ]
+//   "crate_root" : "root file of a Rust target"
+//   "crate_name" : "name of a Rust target"
+//   "rebase" : true or false
+//   "output_conversion" : "string for output conversion"
+//   "response_file_contents": [ list of response file contents entries ]
+// }
+//
+// Optionally, if "what" is specified while generating description, two other
+// properties can be requested that are not included by default. First the
+// runtime dependendencies (see "gn help runtime_deps"):
+//
+//   "runtime_deps" : [list of computed runtime dependencies]
+//
+// Second, for targets whose sources map to outputs (binary targets,
+// action_foreach, and copies with non-constant outputs), the "source_outputs"
+// indicates the mapping from source to output file(s):
+//
+//   "source_outputs" : {
+//      "source_file x" : [ list of outputs for source file x ]
+//      "source_file y" : [ list of outputs for source file y ]
+//      ...
+//   }
+
+namespace {
+
+std::string FormatSourceDir(const SourceDir& dir) {
+#if defined(OS_WIN)
+  // On Windows we fix up system absolute paths to look like native ones.
+  // Internally, they'll look like "/C:\foo\bar/"
+  if (dir.is_system_absolute()) {
+    std::string buf = dir.value();
+    if (buf.size() > 3 && buf[2] == ':') {
+      buf.erase(buf.begin());  // Erase beginning slash.
+      return buf;
+    }
+  }
+#endif
+  return dir.value();
+}
+
+void RecursiveCollectChildDeps(const Target* target,
+                               std::set<const Target*>* result);
+
+void RecursiveCollectDeps(const Target* target,
+                          std::set<const Target*>* result) {
+  if (result->find(target) != result->end())
+    return;  // Already did this target.
+  result->insert(target);
+
+  RecursiveCollectChildDeps(target, result);
+}
+
+void RecursiveCollectChildDeps(const Target* target,
+                               std::set<const Target*>* result) {
+  for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
+    RecursiveCollectDeps(pair.ptr, result);
+}
+
+// Common functionality for target and config description builder
+class BaseDescBuilder {
+ public:
+  using ValuePtr = std::unique_ptr<base::Value>;
+
+  BaseDescBuilder(const std::set<std::string>& what,
+                  bool all,
+                  bool tree,
+                  bool blame)
+      : what_(what), all_(all), tree_(tree), blame_(blame) {}
+
+ protected:
+  virtual Label GetToolchainLabel() const = 0;
+
+  bool what(const std::string& w) const {
+    return what_.empty() || what_.find(w) != what_.end();
+  }
+
+  template <typename T>
+  ValuePtr RenderValue(const std::vector<T>& vector) {
+    auto res = std::make_unique<base::ListValue>();
+    for (const auto& v : vector)
+      res->Append(RenderValue(v));
+
+    return std::move(res);
+  }
+
+  ValuePtr RenderValue(const std::string& s, bool optional = false) {
+    return (s.empty() && optional) ? std::make_unique<base::Value>()
+                                   : ValuePtr(new base::Value(s));
+  }
+
+  ValuePtr RenderValue(const SourceDir& d) {
+    return d.is_null() ? std::make_unique<base::Value>()
+                       : ValuePtr(new base::Value(FormatSourceDir(d)));
+  }
+
+  ValuePtr RenderValue(const SourceFile& f) {
+    return f.is_null() ? std::make_unique<base::Value>()
+                       : ValuePtr(new base::Value(f.value()));
+  }
+
+  ValuePtr RenderValue(const SourceFile* f) { return RenderValue(*f); }
+
+  ValuePtr RenderValue(const LibFile& lib) {
+    if (lib.is_source_file())
+      return RenderValue(lib.source_file());
+    return RenderValue(lib.value());
+  }
+
+  template <typename T>
+  base::Value ToBaseValue(const std::vector<T>& vector) {
+    base::ListValue res;
+    for (const auto& v : vector)
+      res.GetList().emplace_back(ToBaseValue(v));
+    return std::move(res);
+  }
+
+  base::Value ToBaseValue(const Scope* scope) {
+    base::DictionaryValue res;
+    Scope::KeyValueMap map;
+    scope->GetCurrentScopeValues(&map);
+    for (const auto& v : map)
+      res.SetKey(v.first, ToBaseValue(v.second));
+    return std::move(res);
+  }
+
+  base::Value ToBaseValue(const Value& val) {
+    switch (val.type()) {
+      case Value::STRING:
+        return base::Value(val.string_value());
+      case Value::INTEGER:
+        return base::Value(int(val.int_value()));
+      case Value::BOOLEAN:
+        return base::Value(val.boolean_value());
+      case Value::SCOPE:
+        return ToBaseValue(val.scope_value());
+      case Value::LIST:
+        return ToBaseValue(val.list_value());
+      case Value::NONE:
+        return base::Value();
+    }
+    NOTREACHED();
+    return base::Value();
+  }
+
+  template <class VectorType>
+  void FillInConfigVector(base::ListValue* out,
+                          const VectorType& configs,
+                          int indent = 0) {
+    for (const auto& config : configs) {
+      std::string name(indent * 2, ' ');
+      name.append(config.label.GetUserVisibleName(GetToolchainLabel()));
+      out->AppendString(name);
+      if (tree_)
+        FillInConfigVector(out, config.ptr->configs(), indent + 1);
+    }
+  }
+
+  void FillInPrecompiledHeader(base::DictionaryValue* out,
+                               const ConfigValues& values) {
+    if (what(variables::kPrecompiledHeader) &&
+        !values.precompiled_header().empty()) {
+      out->SetWithoutPathExpansion(
+          variables::kPrecompiledHeader,
+          RenderValue(values.precompiled_header(), true));
+    }
+    if (what(variables::kPrecompiledSource) &&
+        !values.precompiled_source().is_null()) {
+      out->SetWithoutPathExpansion(variables::kPrecompiledSource,
+                                   RenderValue(values.precompiled_source()));
+    }
+  }
+
+  std::set<std::string> what_;
+  bool all_;
+  bool tree_;
+  bool blame_;
+};
+
+class ConfigDescBuilder : public BaseDescBuilder {
+ public:
+  ConfigDescBuilder(const Config* config, const std::set<std::string>& what)
+      : BaseDescBuilder(what, false, false, false), config_(config) {}
+
+  std::unique_ptr<base::DictionaryValue> BuildDescription() {
+    auto res = std::make_unique<base::DictionaryValue>();
+    const ConfigValues& values = config_->resolved_values();
+
+    if (what_.empty())
+      res->SetKey(
+          "toolchain",
+          base::Value(
+              config_->label().GetToolchainLabel().GetUserVisibleName(false)));
+
+    if (what(variables::kConfigs) && !config_->configs().empty()) {
+      auto configs = std::make_unique<base::ListValue>();
+      FillInConfigVector(configs.get(), config_->configs().vector());
+      res->SetWithoutPathExpansion(variables::kConfigs, std::move(configs));
+    }
+
+    if (what(variables::kVisibility)) {
+      res->SetWithoutPathExpansion(variables::kVisibility,
+                                   config_->visibility().AsValue());
+    }
+
+#define CONFIG_VALUE_ARRAY_HANDLER(name, type)                        \
+  if (what(#name)) {                                                  \
+    ValuePtr ptr =                                                    \
+        render_config_value_array<type>(values, &ConfigValues::name); \
+    if (ptr) {                                                        \
+      res->SetWithoutPathExpansion(#name, std::move(ptr));            \
+    }                                                                 \
+  }
+    CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(defines, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(frameworks, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(framework_dirs, SourceDir)
+    CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir)
+    CONFIG_VALUE_ARRAY_HANDLER(inputs, SourceFile)
+    CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(lib_dirs, SourceDir)
+    CONFIG_VALUE_ARRAY_HANDLER(libs, LibFile)
+    CONFIG_VALUE_ARRAY_HANDLER(swiftflags, std::string)
+
+#undef CONFIG_VALUE_ARRAY_HANDLER
+
+    FillInPrecompiledHeader(res.get(), values);
+
+    return res;
+  }
+
+ protected:
+  Label GetToolchainLabel() const override {
+    return config_->label().GetToolchainLabel();
+  }
+
+ private:
+  template <typename T>
+  ValuePtr render_config_value_array(
+      const ConfigValues& values,
+      const std::vector<T>& (ConfigValues::*getter)() const) {
+    auto res = std::make_unique<base::ListValue>();
+
+    for (const T& cur : (values.*getter)())
+      res->Append(RenderValue(cur));
+
+    return res->empty() ? nullptr : std::move(res);
+  }
+
+  const Config* config_;
+};
+
+class TargetDescBuilder : public BaseDescBuilder {
+ public:
+  TargetDescBuilder(const Target* target,
+                    const std::set<std::string>& what,
+                    bool all,
+                    bool tree,
+                    bool blame)
+      : BaseDescBuilder(what, all, tree, blame), target_(target) {}
+
+  std::unique_ptr<base::DictionaryValue> BuildDescription() {
+    auto res = std::make_unique<base::DictionaryValue>();
+    bool is_binary_output = target_->IsBinary();
+
+    if (what_.empty()) {
+      res->SetKey(
+          "type",
+          base::Value(Target::GetStringForOutputType(target_->output_type())));
+      res->SetKey(
+          "toolchain",
+          base::Value(
+              target_->label().GetToolchainLabel().GetUserVisibleName(false)));
+    }
+
+    if (target_->source_types_used().RustSourceUsed()) {
+      if (what(variables::kRustCrateRoot)) {
+        res->SetWithoutPathExpansion(
+            variables::kRustCrateRoot,
+            RenderValue(target_->rust_values().crate_root()));
+      }
+      if (what(variables::kRustCrateName)) {
+        res->SetKey(variables::kRustCrateName,
+                    base::Value(target_->rust_values().crate_name()));
+      }
+    }
+
+    if (target_->source_types_used().SwiftSourceUsed()) {
+      if (what(variables::kSwiftBridgeHeader)) {
+        res->SetWithoutPathExpansion(
+            variables::kSwiftBridgeHeader,
+            RenderValue(target_->swift_values().bridge_header()));
+      }
+      if (what(variables::kSwiftModuleName)) {
+        res->SetKey(variables::kSwiftModuleName,
+                    base::Value(target_->swift_values().module_name()));
+      }
+    }
+
+    // General target meta variables.
+
+    if (what(variables::kMetadata)) {
+      base::DictionaryValue metadata;
+      for (const auto& v : target_->metadata().contents())
+        metadata.SetKey(v.first, ToBaseValue(v.second));
+      res->SetKey(variables::kMetadata, std::move(metadata));
+    }
+
+    if (what(variables::kVisibility))
+      res->SetWithoutPathExpansion(variables::kVisibility,
+                                   target_->visibility().AsValue());
+
+    if (what(variables::kTestonly))
+      res->SetKey(variables::kTestonly, base::Value(target_->testonly()));
+
+    if (is_binary_output) {
+      if (what(variables::kCheckIncludes))
+        res->SetKey(variables::kCheckIncludes,
+                    base::Value(target_->check_includes()));
+
+      if (what(variables::kAllowCircularIncludesFrom)) {
+        auto labels = std::make_unique<base::ListValue>();
+        for (const auto& cur : target_->allow_circular_includes_from())
+          labels->AppendString(cur.GetUserVisibleName(GetToolchainLabel()));
+
+        res->SetWithoutPathExpansion(variables::kAllowCircularIncludesFrom,
+                                     std::move(labels));
+      }
+    }
+
+    if (what(variables::kSources) && !target_->sources().empty())
+      res->SetWithoutPathExpansion(variables::kSources,
+                                   RenderValue(target_->sources()));
+
+    if (what(variables::kOutputName) && !target_->output_name().empty())
+      res->SetKey(variables::kOutputName, base::Value(target_->output_name()));
+
+    if (what(variables::kOutputDir) && !target_->output_dir().is_null())
+      res->SetWithoutPathExpansion(variables::kOutputDir,
+                                   RenderValue(target_->output_dir()));
+
+    if (what(variables::kOutputExtension) && target_->output_extension_set())
+      res->SetKey(variables::kOutputExtension,
+                  base::Value(target_->output_extension()));
+
+    if (what(variables::kPublic)) {
+      if (target_->all_headers_public())
+        res->SetKey(variables::kPublic, base::Value("*"));
+      else
+        res->SetWithoutPathExpansion(variables::kPublic,
+                                     RenderValue(target_->public_headers()));
+    }
+
+    if (what(variables::kInputs)) {
+      std::vector<const SourceFile*> inputs;
+      for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+        for (const auto& input : iter.cur().inputs())
+          inputs.push_back(&input);
+      }
+      if (!inputs.empty())
+        res->SetWithoutPathExpansion(variables::kInputs, RenderValue(inputs));
+    }
+
+    if (is_binary_output && what(variables::kConfigs) &&
+        !target_->configs().empty()) {
+      auto configs = std::make_unique<base::ListValue>();
+      FillInConfigVector(configs.get(), target_->configs().vector());
+      res->SetWithoutPathExpansion(variables::kConfigs, std::move(configs));
+    }
+
+    if (what(variables::kPublicConfigs) && !target_->public_configs().empty()) {
+      auto configs = std::make_unique<base::ListValue>();
+      FillInConfigVector(configs.get(), target_->public_configs());
+      res->SetWithoutPathExpansion(variables::kPublicConfigs,
+                                   std::move(configs));
+    }
+
+    if (what(variables::kAllDependentConfigs) &&
+        !target_->all_dependent_configs().empty()) {
+      auto configs = std::make_unique<base::ListValue>();
+      FillInConfigVector(configs.get(), target_->all_dependent_configs());
+      res->SetWithoutPathExpansion(variables::kAllDependentConfigs,
+                                   std::move(configs));
+    }
+
+    // Action
+    if (target_->output_type() == Target::ACTION ||
+        target_->output_type() == Target::ACTION_FOREACH) {
+      if (what(variables::kScript))
+        res->SetKey(variables::kScript,
+                    base::Value(target_->action_values().script().value()));
+
+      if (what(variables::kArgs)) {
+        auto args = std::make_unique<base::ListValue>();
+        for (const auto& elem : target_->action_values().args().list())
+          args->AppendString(elem.AsString());
+
+        res->SetWithoutPathExpansion(variables::kArgs, std::move(args));
+      }
+      if (what(variables::kResponseFileContents) &&
+          !target_->action_values().rsp_file_contents().list().empty()) {
+        auto rsp_file_contents = std::make_unique<base::ListValue>();
+        for (const auto& elem :
+             target_->action_values().rsp_file_contents().list())
+          rsp_file_contents->AppendString(elem.AsString());
+
+        res->SetWithoutPathExpansion(variables::kResponseFileContents,
+                                     std::move(rsp_file_contents));
+      }
+      if (what(variables::kDepfile) &&
+          !target_->action_values().depfile().empty()) {
+        res->SetKey(variables::kDepfile,
+                    base::Value(target_->action_values().depfile().AsString()));
+      }
+    }
+
+    if (target_->output_type() != Target::SOURCE_SET &&
+        target_->output_type() != Target::GROUP &&
+        target_->output_type() != Target::BUNDLE_DATA) {
+      if (what(variables::kOutputs))
+        FillInOutputs(res.get());
+    }
+
+    // Source outputs are only included when specifically asked for it
+    if (what_.find("source_outputs") != what_.end())
+      FillInSourceOutputs(res.get());
+
+    if (target_->output_type() == Target::CREATE_BUNDLE && what("bundle_data"))
+      FillInBundle(res.get());
+
+    if (is_binary_output) {
+#define CONFIG_VALUE_ARRAY_HANDLER(name, type)                    \
+  if (what(#name)) {                                              \
+    ValuePtr ptr = RenderConfigValues<type>(&ConfigValues::name); \
+    if (ptr) {                                                    \
+      res->SetWithoutPathExpansion(#name, std::move(ptr));        \
+    }                                                             \
+  }
+      CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(rustflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(defines, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir)
+      CONFIG_VALUE_ARRAY_HANDLER(inputs, SourceFile)
+      CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(swiftflags, std::string)
+#undef CONFIG_VALUE_ARRAY_HANDLER
+
+      // Libs and lib_dirs are handled specially below.
+
+      if (what(variables::kExterns)) {
+        base::DictionaryValue externs;
+        for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+          const ConfigValues& cur = iter.cur();
+          for (const auto& e : cur.externs()) {
+            externs.SetKey(e.first, base::Value(e.second.value()));
+          }
+        }
+        res->SetKey(variables::kExterns, std::move(externs));
+      }
+
+      FillInPrecompiledHeader(res.get(), target_->config_values());
+    }
+
+    // GeneratedFile vars.
+    if (target_->output_type() == Target::GENERATED_FILE) {
+      if (what(variables::kWriteOutputConversion)) {
+        res->SetKey(variables::kWriteOutputConversion,
+                    ToBaseValue(target_->output_conversion()));
+      }
+      if (what(variables::kDataKeys)) {
+        base::ListValue keys;
+        for (const auto& k : target_->data_keys())
+          keys.GetList().push_back(base::Value(k));
+        res->SetKey(variables::kDataKeys, std::move(keys));
+      }
+      if (what(variables::kRebase)) {
+        res->SetWithoutPathExpansion(variables::kRebase,
+                                     RenderValue(target_->rebase()));
+      }
+      if (what(variables::kWalkKeys)) {
+        base::ListValue keys;
+        for (const auto& k : target_->walk_keys())
+          keys.GetList().push_back(base::Value(k));
+        res->SetKey(variables::kWalkKeys, std::move(keys));
+      }
+    }
+
+    if (what(variables::kDeps))
+      res->SetWithoutPathExpansion(variables::kDeps, RenderDeps());
+
+    // Runtime deps are special, print only when explicitly asked for and not in
+    // overview mode.
+    if (what_.find("runtime_deps") != what_.end())
+      res->SetWithoutPathExpansion("runtime_deps", RenderRuntimeDeps());
+
+    // libs and lib_dirs are special in that they're inherited. We don't
+    // currently implement a blame feature for this since the bottom-up
+    // inheritance makes this difficult.
+
+    // Libs can be part of any target and get recursively pushed up the chain,
+    // so display them regardless of target type.
+    if (what(variables::kLibs)) {
+      const OrderedSet<LibFile>& all_libs = target_->all_libs();
+      if (!all_libs.empty()) {
+        auto libs = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_libs.size(); i++)
+          libs->AppendString(all_libs[i].value());
+        res->SetWithoutPathExpansion(variables::kLibs, std::move(libs));
+      }
+    }
+
+    if (what(variables::kLibDirs)) {
+      const OrderedSet<SourceDir>& all_lib_dirs = target_->all_lib_dirs();
+      if (!all_lib_dirs.empty()) {
+        auto lib_dirs = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_lib_dirs.size(); i++)
+          lib_dirs->AppendString(FormatSourceDir(all_lib_dirs[i]));
+        res->SetWithoutPathExpansion(variables::kLibDirs, std::move(lib_dirs));
+      }
+    }
+
+    if (what(variables::kFrameworks)) {
+      const auto& all_frameworks = target_->all_frameworks();
+      if (!all_frameworks.empty()) {
+        auto frameworks = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_frameworks.size(); i++)
+          frameworks->AppendString(all_frameworks[i]);
+        res->SetWithoutPathExpansion(variables::kFrameworks,
+                                     std::move(frameworks));
+      }
+    }
+    if (what(variables::kWeakFrameworks)) {
+      const auto& weak_frameworks = target_->all_weak_frameworks();
+      if (!weak_frameworks.empty()) {
+        auto frameworks = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < weak_frameworks.size(); i++)
+          frameworks->AppendString(weak_frameworks[i]);
+        res->SetWithoutPathExpansion(variables::kWeakFrameworks,
+                                     std::move(frameworks));
+      }
+    }
+
+    if (what(variables::kFrameworkDirs)) {
+      const auto& all_framework_dirs = target_->all_framework_dirs();
+      if (!all_framework_dirs.empty()) {
+        auto framework_dirs = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_framework_dirs.size(); i++)
+          framework_dirs->AppendString(all_framework_dirs[i].value());
+        res->SetWithoutPathExpansion(variables::kFrameworkDirs,
+                                     std::move(framework_dirs));
+      }
+    }
+
+    return res;
+  }
+
+ private:
+  // Prints dependencies of the given target (not the target itself). If the
+  // set is non-null, new targets encountered will be added to the set, and if
+  // a dependency is in the set already, it will not be recused into. When the
+  // set is null, all dependencies will be printed.
+  void RecursivePrintDeps(base::ListValue* out,
+                          const Target* target,
+                          std::set<const Target*>* seen_targets,
+                          int indent_level) {
+    // Combine all deps into one sorted list.
+    std::vector<LabelTargetPair> sorted_deps;
+    for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
+      sorted_deps.push_back(pair);
+    std::sort(sorted_deps.begin(), sorted_deps.end());
+
+    std::string indent(indent_level * 2, ' ');
+
+    for (const auto& pair : sorted_deps) {
+      const Target* cur_dep = pair.ptr;
+      std::string str =
+          indent + cur_dep->label().GetUserVisibleName(GetToolchainLabel());
+
+      bool print_children = true;
+      if (seen_targets) {
+        if (seen_targets->find(cur_dep) == seen_targets->end()) {
+          // New target, mark it visited.
+          seen_targets->insert(cur_dep);
+        } else {
+          // Already seen.
+          print_children = false;
+          // Only print "..." if something is actually elided, which means that
+          // the current target has children.
+          if (!cur_dep->public_deps().empty() ||
+              !cur_dep->private_deps().empty() || !cur_dep->data_deps().empty())
+            str += "...";
+        }
+      }
+
+      out->AppendString(str);
+
+      if (print_children)
+        RecursivePrintDeps(out, cur_dep, seen_targets, indent_level + 1);
+    }
+  }
+
+  ValuePtr RenderDeps() {
+    auto res = std::make_unique<base::ListValue>();
+
+    // Tree mode is separate.
+    if (tree_) {
+      if (all_) {
+        // Show all tree deps with no eliding.
+        RecursivePrintDeps(res.get(), target_, nullptr, 0);
+      } else {
+        // Don't recurse into duplicates.
+        std::set<const Target*> seen_targets;
+        RecursivePrintDeps(res.get(), target_, &seen_targets, 0);
+      }
+    } else {  // not tree
+
+      // Collect the deps to display.
+      if (all_) {
+        // Show all dependencies.
+        std::set<const Target*> all_deps;
+        RecursiveCollectChildDeps(target_, &all_deps);
+        commands::FilterAndPrintTargetSet(all_deps, res.get());
+      } else {
+        // Show direct dependencies only.
+        std::vector<const Target*> deps;
+        for (const auto& pair : target_->GetDeps(Target::DEPS_ALL))
+          deps.push_back(pair.ptr);
+        std::sort(deps.begin(), deps.end());
+        commands::FilterAndPrintTargets(&deps, res.get());
+      }
+    }
+
+    return std::move(res);
+  }
+
+  ValuePtr RenderRuntimeDeps() {
+    auto res = std::make_unique<base::ListValue>();
+
+    const Target* previous_from = NULL;
+    for (const auto& pair : ComputeRuntimeDeps(target_)) {
+      std::string str;
+      if (blame_) {
+        // Generally a target's runtime deps will be listed sequentially, so
+        // group them and don't duplicate the "from" label for two in a row.
+        if (previous_from == pair.second) {
+          str = "  ";
+        } else {
+          previous_from = pair.second;
+          res->AppendString(
+              str + "From " +
+              pair.second->label().GetUserVisibleName(GetToolchainLabel()));
+          str = "  ";
+        }
+      }
+
+      res->AppendString(str + pair.first.value());
+    }
+
+    return std::move(res);
+  }
+
+  void FillInSourceOutputs(base::DictionaryValue* res) {
+    // Only include "source outputs" if there are sources that map to outputs.
+    // Things like actions have constant per-target outputs that don't depend on
+    // the list of sources. These don't need source outputs.
+    if (target_->output_type() != Target::ACTION_FOREACH &&
+        target_->output_type() != Target::COPY_FILES && !target_->IsBinary())
+      return;  // Everything else has constant outputs.
+
+    // "copy" targets may have patterns or not. If there's only one file, the
+    // user can specify a constant output name.
+    if (target_->output_type() == Target::COPY_FILES &&
+        target_->action_values().outputs().required_types().empty())
+      return;  // Constant output.
+
+    auto dict = std::make_unique<base::DictionaryValue>();
+    for (const auto& source : target_->sources()) {
+      std::vector<OutputFile> outputs;
+      const char* tool_name = Tool::kToolNone;
+      if (target_->GetOutputFilesForSource(source, &tool_name, &outputs)) {
+        auto list = std::make_unique<base::ListValue>();
+        for (const auto& output : outputs)
+          list->AppendString(output.value());
+
+        dict->SetWithoutPathExpansion(source.value(), std::move(list));
+      }
+    }
+    res->SetWithoutPathExpansion("source_outputs", std::move(dict));
+  }
+
+  void FillInBundle(base::DictionaryValue* res) {
+    auto data = std::make_unique<base::DictionaryValue>();
+    const BundleData& bundle_data = target_->bundle_data();
+    const Settings* settings = target_->settings();
+    BundleData::SourceFiles sources;
+    bundle_data.GetSourceFiles(&sources);
+    data->SetWithoutPathExpansion("source_files", RenderValue(sources));
+    data->SetKey(
+        "root_dir_output",
+        base::Value(bundle_data.GetBundleRootDirOutput(settings).value()));
+    data->SetWithoutPathExpansion("root_dir",
+                                  RenderValue(bundle_data.root_dir()));
+    data->SetWithoutPathExpansion("resources_dir",
+                                  RenderValue(bundle_data.resources_dir()));
+    data->SetWithoutPathExpansion("executable_dir",
+                                  RenderValue(bundle_data.executable_dir()));
+    data->SetKey("product_type", base::Value(bundle_data.product_type()));
+    data->SetWithoutPathExpansion(
+        "partial_info_plist", RenderValue(bundle_data.partial_info_plist()));
+
+    auto deps = std::make_unique<base::ListValue>();
+    for (const auto* dep : bundle_data.bundle_deps())
+      deps->AppendString(dep->label().GetUserVisibleName(GetToolchainLabel()));
+
+    data->SetWithoutPathExpansion("deps", std::move(deps));
+    res->SetWithoutPathExpansion("bundle_data", std::move(data));
+  }
+
+  void FillInOutputs(base::DictionaryValue* res) {
+    std::vector<SourceFile> output_files;
+    Err err;
+    if (!target_->GetOutputsAsSourceFiles(LocationRange(), true, &output_files,
+                                          &err)) {
+      err.PrintToStdout();
+      return;
+    }
+    res->SetWithoutPathExpansion(variables::kOutputs,
+                                 RenderValue(output_files));
+
+    // Write some extra data for certain output types.
+    if (target_->output_type() == Target::ACTION_FOREACH ||
+        target_->output_type() == Target::COPY_FILES) {
+      const SubstitutionList& outputs = target_->action_values().outputs();
+      if (!outputs.required_types().empty()) {
+        // Write out the output patterns if there are any.
+        auto patterns = std::make_unique<base::ListValue>();
+        for (const auto& elem : outputs.list())
+          patterns->AppendString(elem.AsString());
+
+        res->SetWithoutPathExpansion("output_patterns", std::move(patterns));
+      }
+    }
+  }
+
+  // Writes a given config value type to the string, optionally with
+  // attribution.
+  // This should match RecursiveTargetConfigToStream in the order it traverses.
+  template <class T>
+  ValuePtr RenderConfigValues(const std::vector<T>& (ConfigValues::*getter)()
+                                  const) {
+    auto res = std::make_unique<base::ListValue>();
+    for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+      const std::vector<T>& vec = (iter.cur().*getter)();
+
+      if (vec.empty())
+        continue;
+
+      if (blame_) {
+        const Config* config = iter.GetCurrentConfig();
+        if (config) {
+          // Source of this value is a config.
+          std::string from =
+              "From " + config->label().GetUserVisibleName(false);
+          res->AppendString(from);
+          if (iter.origin()) {
+            Location location = iter.origin()->GetRange().begin();
+            from = "     (Added by " + location.file()->name().value() + ":" +
+                   base::IntToString(location.line_number()) + ")";
+            res->AppendString(from);
+          }
+        } else {
+          // Source of this value is the target itself.
+          std::string from =
+              "From " + target_->label().GetUserVisibleName(false);
+          res->AppendString(from);
+        }
+      }
+
+      for (const T& val : vec) {
+        ValuePtr rendered = RenderValue(val);
+        std::string str;
+        // Indent string values in blame mode
+        if (blame_ && rendered->GetAsString(&str)) {
+          str = "  " + str;
+          rendered = std::make_unique<base::Value>(str);
+        }
+        res->Append(std::move(rendered));
+      }
+    }
+    return res->empty() ? nullptr : std::move(res);
+  }
+
+  Label GetToolchainLabel() const override {
+    return target_->label().GetToolchainLabel();
+  }
+
+  const Target* target_;
+};
+
+}  // namespace
+
+std::unique_ptr<base::DictionaryValue> DescBuilder::DescriptionForTarget(
+    const Target* target,
+    const std::string& what,
+    bool all,
+    bool tree,
+    bool blame) {
+  std::set<std::string> w;
+  if (!what.empty())
+    w.insert(what);
+  TargetDescBuilder b(target, w, all, tree, blame);
+  return b.BuildDescription();
+}
+
+std::unique_ptr<base::DictionaryValue> DescBuilder::DescriptionForConfig(
+    const Config* config,
+    const std::string& what) {
+  std::set<std::string> w;
+  if (!what.empty())
+    w.insert(what);
+  ConfigDescBuilder b(config, w);
+  return b.BuildDescription();
+}
diff --git a/src/gn/desc_builder.h b/src/gn/desc_builder.h
new file mode 100644 (file)
index 0000000..e0e95a3
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_DESC_BUILDER_H_
+#define TOOLS_GN_DESC_BUILDER_H_
+
+#include "base/values.h"
+#include "gn/target.h"
+
+class DescBuilder {
+ public:
+  // Creates Dictionary representation for given target
+  static std::unique_ptr<base::DictionaryValue> DescriptionForTarget(
+      const Target* target,
+      const std::string& what,
+      bool all,
+      bool tree,
+      bool blame);
+
+  // Creates Dictionary representation for given config
+  static std::unique_ptr<base::DictionaryValue> DescriptionForConfig(
+      const Config* config,
+      const std::string& what);
+};
+
+#endif
diff --git a/src/gn/eclipse_writer.cc b/src/gn/eclipse_writer.cc
new file mode 100644 (file)
index 0000000..0067f6d
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/eclipse_writer.h"
+
+#include <fstream>
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "gn/builder.h"
+#include "gn/config_values_extractors.h"
+#include "gn/filesystem_utils.h"
+#include "gn/loader.h"
+#include "gn/xml_element_writer.h"
+
+namespace {
+
+// Escapes |unescaped| for use in XML element content.
+std::string EscapeForXML(const std::string& unescaped) {
+  std::string result;
+  result.reserve(unescaped.length());
+  for (const char c : unescaped) {
+    if (c == '<')
+      result += "&lt;";
+    else if (c == '>')
+      result += "&gt;";
+    else if (c == '&')
+      result += "&amp;";
+    else
+      result.push_back(c);
+  }
+  return result;
+}
+
+}  // namespace
+
+EclipseWriter::EclipseWriter(const BuildSettings* build_settings,
+                             const Builder& builder,
+                             std::ostream& out)
+    : build_settings_(build_settings), builder_(builder), out_(out) {
+  languages_.push_back("C++ Source File");
+  languages_.push_back("C Source File");
+  languages_.push_back("Assembly Source File");
+  languages_.push_back("GNU C++");
+  languages_.push_back("GNU C");
+  languages_.push_back("Assembly");
+}
+
+EclipseWriter::~EclipseWriter() = default;
+
+// static
+bool EclipseWriter::RunAndWriteFile(const BuildSettings* build_settings,
+                                    const Builder& builder,
+                                    Err* err) {
+  base::FilePath file = build_settings->GetFullPath(build_settings->build_dir())
+                            .AppendASCII("eclipse-cdt-settings.xml");
+  std::ofstream file_out;
+  file_out.open(FilePathToUTF8(file).c_str(),
+                std::ios_base::out | std::ios_base::binary);
+  if (file_out.fail()) {
+    *err =
+        Err(Location(), "Couldn't open eclipse-cdt-settings.xml for writing");
+    return false;
+  }
+
+  EclipseWriter gen(build_settings, builder, file_out);
+  gen.Run();
+  return true;
+}
+
+void EclipseWriter::Run() {
+  GetAllIncludeDirs();
+  GetAllDefines();
+  WriteCDTSettings();
+}
+
+void EclipseWriter::GetAllIncludeDirs() {
+  std::vector<const Target*> targets = builder_.GetAllResolvedTargets();
+  for (const Target* target : targets) {
+    if (!UsesDefaultToolchain(target))
+      continue;
+
+    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+      for (const SourceDir& include_dir : it.cur().include_dirs()) {
+        include_dirs_.insert(
+            FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
+      }
+    }
+  }
+}
+
+void EclipseWriter::GetAllDefines() {
+  std::vector<const Target*> targets = builder_.GetAllResolvedTargets();
+  for (const Target* target : targets) {
+    if (!UsesDefaultToolchain(target))
+      continue;
+
+    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+      for (const std::string& define : it.cur().defines()) {
+        size_t equal_pos = define.find('=');
+        std::string define_key;
+        std::string define_value;
+        if (equal_pos == std::string::npos) {
+          define_key = define;
+        } else {
+          define_key = define.substr(0, equal_pos);
+          define_value = define.substr(equal_pos + 1);
+        }
+        defines_[define_key] = define_value;
+      }
+    }
+  }
+}
+
+bool EclipseWriter::UsesDefaultToolchain(const Target* target) const {
+  return target->toolchain()->label() ==
+         builder_.loader()->GetDefaultToolchain();
+}
+
+void EclipseWriter::WriteCDTSettings() {
+  out_ << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
+  XmlElementWriter cdt_properties_element(out_, "cdtprojectproperties",
+                                          XmlAttributes());
+
+  {
+    const char* kIncludesSectionName =
+        "org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths";
+    std::unique_ptr<XmlElementWriter> section_element =
+        cdt_properties_element.SubElement(
+            "section", XmlAttributes("name", kIncludesSectionName));
+
+    section_element->SubElement(
+        "language", XmlAttributes("name", "holder for library settings"));
+
+    for (const std::string& language : languages_) {
+      std::unique_ptr<XmlElementWriter> language_element =
+          section_element->SubElement("language",
+                                      XmlAttributes("name", language));
+      for (const std::string& include_dir : include_dirs_) {
+        language_element
+            ->SubElement("includepath",
+                         XmlAttributes("workspace_path", "false"))
+            ->Text(EscapeForXML(include_dir));
+      }
+    }
+  }
+
+  {
+    const char* kMacrosSectionName =
+        "org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros";
+    std::unique_ptr<XmlElementWriter> section_element =
+        cdt_properties_element.SubElement(
+            "section", XmlAttributes("name", kMacrosSectionName));
+
+    section_element->SubElement(
+        "language", XmlAttributes("name", "holder for library settings"));
+
+    for (const std::string& language : languages_) {
+      std::unique_ptr<XmlElementWriter> language_element =
+          section_element->SubElement("language",
+                                      XmlAttributes("name", language));
+      for (const auto& key_val : defines_) {
+        std::unique_ptr<XmlElementWriter> macro_element =
+            language_element->SubElement("macro");
+        macro_element->SubElement("name")->Text(EscapeForXML(key_val.first));
+        macro_element->SubElement("value")->Text(EscapeForXML(key_val.second));
+      }
+    }
+  }
+}
diff --git a/src/gn/eclipse_writer.h b/src/gn/eclipse_writer.h
new file mode 100644 (file)
index 0000000..e15254f
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ECLIPSE_WRITER_H_
+#define TOOLS_GN_ECLIPSE_WRITER_H_
+
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class BuildSettings;
+class Builder;
+class Err;
+class Target;
+
+class EclipseWriter {
+ public:
+  static bool RunAndWriteFile(const BuildSettings* build_settings,
+                              const Builder& builder,
+                              Err* err);
+
+ private:
+  EclipseWriter(const BuildSettings* build_settings,
+                const Builder& builder,
+                std::ostream& out);
+  ~EclipseWriter();
+
+  void Run();
+
+  // Populates |include_dirs_| with the include dirs of all the targets for the
+  // default toolchain.
+  void GetAllIncludeDirs();
+
+  // Populates |defines_| with the defines of all the targets for the default
+  // toolchain.
+  void GetAllDefines();
+
+  // Returns true if |target| uses the default toolchain.
+  bool UsesDefaultToolchain(const Target* target) const;
+
+  // Writes the XML settings file.
+  void WriteCDTSettings();
+
+  const BuildSettings* build_settings_;
+  const Builder& builder_;
+
+  // The output stream for the settings file.
+  std::ostream& out_;
+
+  // Eclipse languages for which the include dirs and defines apply.
+  std::vector<std::string> languages_;
+
+  // The include dirs of all the targets which use the default toolchain.
+  std::set<std::string> include_dirs_;
+
+  // The defines of all the targets which use the default toolchain.
+  std::map<std::string, std::string> defines_;
+
+  DISALLOW_COPY_AND_ASSIGN(EclipseWriter);
+};
+
+#endif  // TOOLS_GN_ECLIPSE_WRITER_H_
diff --git a/src/gn/err.cc b/src/gn/err.cc
new file mode 100644 (file)
index 0000000..301ee8d
--- /dev/null
@@ -0,0 +1,200 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/err.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/standard_out.h"
+#include "gn/tokenizer.h"
+#include "gn/value.h"
+
+namespace {
+
+std::string GetNthLine(const std::string_view& data, int n) {
+  size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n);
+  size_t end = line_off + 1;
+  while (end < data.size() && !Tokenizer::IsNewline(data, end))
+    end++;
+  return std::string(data.substr(line_off, end - line_off));
+}
+
+void FillRangeOnLine(const LocationRange& range,
+                     int line_number,
+                     std::string* line) {
+  // Only bother if the range's begin or end overlaps the line. If the entire
+  // line is highlighted as a result of this range, it's not very helpful.
+  if (range.begin().line_number() != line_number &&
+      range.end().line_number() != line_number)
+    return;
+
+  // Watch out, the char offsets in the location are 1-based, so we have to
+  // subtract 1.
+  int begin_char;
+  if (range.begin().line_number() < line_number)
+    begin_char = 0;
+  else
+    begin_char = range.begin().column_number() - 1;
+
+  int end_char;
+  if (range.end().line_number() > line_number)
+    end_char = static_cast<int>(line->size());  // Ending is non-inclusive.
+  else
+    end_char = range.end().column_number() - 1;
+
+  CHECK(end_char >= begin_char);
+  CHECK(begin_char >= 0 && begin_char <= static_cast<int>(line->size()));
+  CHECK(end_char >= 0 && end_char <= static_cast<int>(line->size()));
+  for (int i = begin_char; i < end_char; i++)
+    line->at(i) = '-';
+}
+
+// The line length is used to clip the maximum length of the markers we'll
+// make if the error spans more than one line (like unterminated literals).
+void OutputHighlighedPosition(const Location& location,
+                              const Err::RangeList& ranges,
+                              size_t line_length) {
+  // Make a buffer of the line in spaces.
+  std::string highlight;
+  highlight.resize(line_length);
+  for (size_t i = 0; i < line_length; i++)
+    highlight[i] = ' ';
+
+  // Highlight all the ranges on the line.
+  for (const auto& range : ranges)
+    FillRangeOnLine(range, location.line_number(), &highlight);
+
+  // Allow the marker to be one past the end of the line for marking the end.
+  highlight.push_back(' ');
+  CHECK(location.column_number() - 1 >= 0 &&
+        location.column_number() - 1 < static_cast<int>(highlight.size()));
+  highlight[location.column_number() - 1] = '^';
+
+  // Trim unused spaces from end of line.
+  while (!highlight.empty() && highlight[highlight.size() - 1] == ' ')
+    highlight.resize(highlight.size() - 1);
+
+  highlight += "\n";
+  OutputString(highlight, DECORATION_BLUE);
+}
+
+}  // namespace
+
+Err::Err() : has_error_(false) {}
+
+Err::Err(const Location& location,
+         const std::string& msg,
+         const std::string& help)
+    : has_error_(true), location_(location), message_(msg), help_text_(help) {}
+
+Err::Err(const LocationRange& range,
+         const std::string& msg,
+         const std::string& help)
+    : has_error_(true),
+      location_(range.begin()),
+      message_(msg),
+      help_text_(help) {
+  ranges_.push_back(range);
+}
+
+Err::Err(const Token& token, const std::string& msg, const std::string& help)
+    : has_error_(true),
+      location_(token.location()),
+      message_(msg),
+      help_text_(help) {
+  ranges_.push_back(token.range());
+}
+
+Err::Err(const ParseNode* node,
+         const std::string& msg,
+         const std::string& help_text)
+    : has_error_(true), message_(msg), help_text_(help_text) {
+  // Node will be null in certain tests.
+  if (node) {
+    LocationRange range = node->GetRange();
+    location_ = range.begin();
+    ranges_.push_back(range);
+  }
+}
+
+Err::Err(const Value& value,
+         const std::string msg,
+         const std::string& help_text)
+    : has_error_(true), message_(msg), help_text_(help_text) {
+  if (value.origin()) {
+    LocationRange range = value.origin()->GetRange();
+    location_ = range.begin();
+    ranges_.push_back(range);
+  }
+}
+
+Err::Err(const Err& other) = default;
+
+Err::~Err() = default;
+
+void Err::PrintToStdout() const {
+  InternalPrintToStdout(false, true);
+}
+
+void Err::PrintNonfatalToStdout() const {
+  InternalPrintToStdout(false, false);
+}
+
+void Err::AppendSubErr(const Err& err) {
+  sub_errs_.push_back(err);
+}
+
+void Err::InternalPrintToStdout(bool is_sub_err, bool is_fatal) const {
+  DCHECK(has_error_);
+
+  if (!is_sub_err) {
+    if (is_fatal)
+      OutputString("ERROR ", DECORATION_RED);
+    else
+      OutputString("WARNING ", DECORATION_RED);
+  }
+
+  // File name and location.
+  const InputFile* input_file = location_.file();
+  std::string loc_str = location_.Describe(true);
+  if (!loc_str.empty()) {
+    if (is_sub_err)
+      loc_str.insert(0, "See ");
+    else
+      loc_str.insert(0, "at ");
+    if (!toolchain_label_.is_null())
+      loc_str += " ";
+  }
+  std::string toolchain_str;
+  if (!toolchain_label_.is_null()) {
+    toolchain_str += "(" + toolchain_label_.GetUserVisibleName(false) + ")";
+  }
+  std::string colon;
+  if (!loc_str.empty() || !toolchain_str.empty())
+    colon = ": ";
+  OutputString(loc_str + toolchain_str + colon + message_ + "\n");
+
+  // Quoted line.
+  if (input_file) {
+    std::string line =
+        GetNthLine(input_file->contents(), location_.line_number());
+    if (!base::ContainsOnlyChars(line, base::kWhitespaceASCII)) {
+      OutputString(line + "\n", DECORATION_DIM);
+      OutputHighlighedPosition(location_, ranges_, line.size());
+    }
+  }
+
+  // Optional help text.
+  if (!help_text_.empty())
+    OutputString(help_text_ + "\n");
+
+  // Sub errors.
+  for (const auto& sub_err : sub_errs_)
+    sub_err.InternalPrintToStdout(true, is_fatal);
+}
diff --git a/src/gn/err.h b/src/gn/err.h
new file mode 100644 (file)
index 0000000..6bd2f02
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ERR_H_
+#define TOOLS_GN_ERR_H_
+
+#include <string>
+#include <vector>
+
+#include "gn/label.h"
+#include "gn/location.h"
+#include "gn/token.h"
+
+class ParseNode;
+class Value;
+
+// Result of doing some operation. Check has_error() to see if an error
+// occurred.
+//
+// An error has a location and a message. Below that, is some optional help
+// text to go with the annotation of the location.
+//
+// An error can also have sub-errors which are additionally printed out
+// below. They can provide additional context.
+class Err {
+ public:
+  using RangeList = std::vector<LocationRange>;
+
+  // Indicates no error.
+  Err();
+
+  // Error at a single point.
+  Err(const Location& location,
+      const std::string& msg,
+      const std::string& help = std::string());
+
+  // Error at a given range.
+  Err(const LocationRange& range,
+      const std::string& msg,
+      const std::string& help = std::string());
+
+  // Error at a given token.
+  Err(const Token& token,
+      const std::string& msg,
+      const std::string& help_text = std::string());
+
+  // Error at a given node.
+  Err(const ParseNode* node,
+      const std::string& msg,
+      const std::string& help_text = std::string());
+
+  // Error at a given value.
+  Err(const Value& value,
+      const std::string msg,
+      const std::string& help_text = std::string());
+
+  Err(const Err& other);
+
+  ~Err();
+
+  bool has_error() const { return has_error_; }
+  const Location& location() const { return location_; }
+  const std::string& message() const { return message_; }
+  const std::string& help_text() const { return help_text_; }
+
+  void AppendRange(const LocationRange& range) { ranges_.push_back(range); }
+  const RangeList& ranges() const { return ranges_; }
+
+  void set_toolchain_label(const Label& toolchain_label) {
+    toolchain_label_ = toolchain_label;
+  }
+
+  void AppendSubErr(const Err& err);
+
+  void PrintToStdout() const;
+
+  // Prints to standard out but uses a "WARNING" messaging instead of the
+  // normal "ERROR" messaging. This is a property of the printing system rather
+  // than of the Err class because there is no expectation that code calling a
+  // function that take an Err check that the error is nonfatal and continue.
+  // Generally all Err objects with has_error() set are fatal.
+  //
+  // In some very specific cases code will detect a condition and print a
+  // nonfatal error to the screen instead of returning it. In these cases, that
+  // code can decide at printing time whether it will continue (and use this
+  // method) or not (and use PrintToStdout()).
+  void PrintNonfatalToStdout() const;
+
+ private:
+  void InternalPrintToStdout(bool is_sub_err, bool is_fatal) const;
+
+  bool has_error_;
+  Location location_;
+  Label toolchain_label_;
+
+  std::vector<LocationRange> ranges_;
+
+  std::string message_;
+  std::string help_text_;
+
+  std::vector<Err> sub_errs_;
+};
+
+#endif  // TOOLS_GN_ERR_H_
diff --git a/src/gn/escape.cc b/src/gn/escape.cc
new file mode 100644 (file)
index 0000000..3c8dc3f
--- /dev/null
@@ -0,0 +1,312 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/escape.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "util/build_config.h"
+
+namespace {
+
+constexpr size_t kStackStringBufferSize = 1024;
+#if defined(OS_WIN)
+constexpr size_t kMaxEscapedCharsPerChar = 2;
+#else
+constexpr size_t kMaxEscapedCharsPerChar = 3;
+#endif
+
+// A "1" in this lookup table means that char is valid in the Posix shell.
+// clang-format off
+const char kShellValid[0x80] = {
+// 00-1f: all are invalid
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+// ' ' !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
+//  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
+//  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+//  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+//  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+//  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
+// clang-format on
+
+size_t EscapeStringToString_Space(const std::string_view& str,
+                                  const EscapeOptions& options,
+                                  char* dest,
+                                  bool* needed_quoting) {
+  size_t i = 0;
+  for (const auto& elem : str) {
+    if (elem == ' ')
+      dest[i++] = '\\';
+    dest[i++] = elem;
+  }
+  return i;
+}
+
+// Uses the stack if the space needed is small and the heap otherwise.
+class StackOrHeapBuffer {
+ public:
+  explicit StackOrHeapBuffer(size_t buf_size) {
+    if (UNLIKELY(buf_size > kStackStringBufferSize))
+      heap_buf.reset(new char[buf_size]);
+  }
+  operator char*() { return heap_buf ? heap_buf.get() : stack_buf; }
+
+ private:
+  char stack_buf[kStackStringBufferSize];
+  std::unique_ptr<char[]> heap_buf;
+};
+
+// Ninja's escaping rules are very simple. We always escape colons even
+// though they're OK in many places, in case the resulting string is used on
+// the left-hand-side of a rule.
+inline bool ShouldEscapeCharForNinja(char ch) {
+  return ch == '$' || ch == ' ' || ch == ':';
+}
+
+size_t EscapeStringToString_Ninja(const std::string_view& str,
+                                  const EscapeOptions& options,
+                                  char* dest,
+                                  bool* needed_quoting) {
+  size_t i = 0;
+  for (const auto& elem : str) {
+    if (ShouldEscapeCharForNinja(elem))
+      dest[i++] = '$';
+    dest[i++] = elem;
+  }
+  return i;
+}
+
+inline bool ShouldEscapeCharForCompilationDatabase(char ch) {
+  return ch == '\\' || ch == '"';
+}
+
+size_t EscapeStringToString_CompilationDatabase(const std::string_view& str,
+                                                const EscapeOptions& options,
+                                                char* dest,
+                                                bool* needed_quoting) {
+  size_t i = 0;
+  bool quote = false;
+  for (const auto& elem : str) {
+    if (static_cast<unsigned>(elem) >= 0x80 ||
+        !kShellValid[static_cast<int>(elem)]) {
+      quote = true;
+      break;
+    }
+  }
+  if (quote)
+    dest[i++] = '"';
+
+  for (const auto& elem : str) {
+    if (ShouldEscapeCharForCompilationDatabase(elem))
+      dest[i++] = '\\';
+    dest[i++] = elem;
+  }
+  if (quote)
+    dest[i++] = '"';
+  return i;
+}
+
+size_t EscapeStringToString_Depfile(const std::string_view& str,
+                                    const EscapeOptions& options,
+                                    char* dest,
+                                    bool* needed_quoting) {
+  size_t i = 0;
+  for (const auto& elem : str) {
+    // Escape all characters that ninja depfile parser can recognize as escaped,
+    // even if some of them can work without escaping.
+    if (elem == ' ' || elem == '\\' || elem == '#' || elem == '*' ||
+        elem == '[' || elem == '|' || elem == ']')
+      dest[i++] = '\\';
+    else if (elem == '$')  // Extra rule for $$
+      dest[i++] = '$';
+    dest[i++] = elem;
+  }
+  return i;
+}
+
+size_t EscapeStringToString_NinjaPreformatted(const std::string_view& str,
+                                              char* dest) {
+  // Only Ninja-escape $.
+  size_t i = 0;
+  for (const auto& elem : str) {
+    if (elem == '$')
+      dest[i++] = '$';
+    dest[i++] = elem;
+  }
+  return i;
+}
+
+// Escape for CommandLineToArgvW and additionally escape Ninja characters.
+//
+// The basic algorithm is if the string doesn't contain any parse-affecting
+// characters, don't do anything (other than the Ninja processing). If it does,
+// quote the string, and backslash-escape all quotes and backslashes.
+// See:
+//   http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
+//   http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
+size_t EscapeStringToString_WindowsNinjaFork(const std::string_view& str,
+                                             const EscapeOptions& options,
+                                             char* dest,
+                                             bool* needed_quoting) {
+  // We assume we don't have any whitespace chars that aren't spaces.
+  DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos);
+
+  size_t i = 0;
+  if (str.find_first_of(" \"") == std::string::npos) {
+    // Simple case, don't quote.
+    return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
+  } else {
+    if (!options.inhibit_quoting)
+      dest[i++] = '"';
+
+    for (size_t j = 0; j < str.size(); j++) {
+      // Count backslashes in case they're followed by a quote.
+      size_t backslash_count = 0;
+      while (j < str.size() && str[j] == '\\') {
+        j++;
+        backslash_count++;
+      }
+      if (j == str.size()) {
+        // Backslashes at end of string. Backslash-escape all of them since
+        // they'll be followed by a quote.
+        memset(dest + i, '\\', backslash_count * 2);
+        i += backslash_count * 2;
+      } else if (str[j] == '"') {
+        // 0 or more backslashes followed by a quote. Backslash-escape the
+        // backslashes, then backslash-escape the quote.
+        memset(dest + i, '\\', backslash_count * 2 + 1);
+        i += backslash_count * 2 + 1;
+        dest[i++] = '"';
+      } else {
+        // Non-special Windows character, just escape for Ninja. Also, add any
+        // backslashes we read previously, these are literals.
+        memset(dest + i, '\\', backslash_count);
+        i += backslash_count;
+        if (ShouldEscapeCharForNinja(str[j]))
+          dest[i++] = '$';
+        dest[i++] = str[j];
+      }
+    }
+
+    if (!options.inhibit_quoting)
+      dest[i++] = '"';
+    if (needed_quoting)
+      *needed_quoting = true;
+  }
+  return i;
+}
+
+size_t EscapeStringToString_PosixNinjaFork(const std::string_view& str,
+                                           const EscapeOptions& options,
+                                           char* dest,
+                                           bool* needed_quoting) {
+  size_t i = 0;
+  for (const auto& elem : str) {
+    if (elem == '$' || elem == ' ') {
+      // Space and $ are special to both Ninja and the shell. '$' escape for
+      // Ninja, then backslash-escape for the shell.
+      dest[i++] = '\\';
+      dest[i++] = '$';
+      dest[i++] = elem;
+    } else if (elem == ':') {
+      // Colon is the only other Ninja special char, which is not special to
+      // the shell.
+      dest[i++] = '$';
+      dest[i++] = ':';
+    } else if (static_cast<unsigned>(elem) >= 0x80 ||
+               !kShellValid[static_cast<int>(elem)]) {
+      // All other invalid shell chars get backslash-escaped.
+      dest[i++] = '\\';
+      dest[i++] = elem;
+    } else {
+      // Everything else is a literal.
+      dest[i++] = elem;
+    }
+  }
+  return i;
+}
+
+// Escapes |str| into |dest| and returns the number of characters written.
+size_t EscapeStringToString(const std::string_view& str,
+                            const EscapeOptions& options,
+                            char* dest,
+                            bool* needed_quoting) {
+  switch (options.mode) {
+    case ESCAPE_NONE:
+      strncpy(dest, str.data(), str.size());
+      return str.size();
+    case ESCAPE_SPACE:
+      return EscapeStringToString_Space(str, options, dest, needed_quoting);
+    case ESCAPE_NINJA:
+      return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
+    case ESCAPE_DEPFILE:
+      return EscapeStringToString_Depfile(str, options, dest, needed_quoting);
+    case ESCAPE_COMPILATION_DATABASE:
+      return EscapeStringToString_CompilationDatabase(str, options, dest,
+                                                      needed_quoting);
+    case ESCAPE_NINJA_COMMAND:
+      switch (options.platform) {
+        case ESCAPE_PLATFORM_CURRENT:
+#if defined(OS_WIN)
+          return EscapeStringToString_WindowsNinjaFork(str, options, dest,
+                                                       needed_quoting);
+#else
+          return EscapeStringToString_PosixNinjaFork(str, options, dest,
+                                                     needed_quoting);
+#endif
+        case ESCAPE_PLATFORM_WIN:
+          return EscapeStringToString_WindowsNinjaFork(str, options, dest,
+                                                       needed_quoting);
+        case ESCAPE_PLATFORM_POSIX:
+          return EscapeStringToString_PosixNinjaFork(str, options, dest,
+                                                     needed_quoting);
+        default:
+          NOTREACHED();
+      }
+    case ESCAPE_NINJA_PREFORMATTED_COMMAND:
+      return EscapeStringToString_NinjaPreformatted(str, dest);
+    default:
+      NOTREACHED();
+  }
+  return 0;
+}
+
+}  // namespace
+
+std::string EscapeString(const std::string_view& str,
+                         const EscapeOptions& options,
+                         bool* needed_quoting) {
+  StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar);
+  return std::string(dest,
+                     EscapeStringToString(str, options, dest, needed_quoting));
+}
+
+void EscapeStringToStream(std::ostream& out,
+                          const std::string_view& str,
+                          const EscapeOptions& options) {
+  StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar);
+  out.write(dest, EscapeStringToString(str, options, dest, nullptr));
+}
+
+void EscapeJSONStringToStream(std::ostream& out,
+                              const std::string_view& str,
+                              const EscapeOptions& options) {
+  std::string dest;
+  bool needed_quoting = !options.inhibit_quoting;
+  base::EscapeJSONString(str, needed_quoting, &dest);
+
+  EscapeStringToStream(out, dest, options);
+}
diff --git a/src/gn/escape.h b/src/gn/escape.h
new file mode 100644 (file)
index 0000000..31f972e
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ESCAPE_H_
+#define TOOLS_GN_ESCAPE_H_
+
+#include <iosfwd>
+#include <string_view>
+
+enum EscapingMode {
+  // No escaping.
+  ESCAPE_NONE,
+
+  // Space only.
+  ESCAPE_SPACE,
+
+  // Ninja string escaping.
+  ESCAPE_NINJA,
+
+  // Ninja/makefile depfile string escaping.
+  ESCAPE_DEPFILE,
+
+  // For writing commands to ninja files. This assumes the output is "one
+  // thing" like a filename, so will escape or quote spaces as necessary for
+  // both Ninja and the shell to keep that thing together.
+  ESCAPE_NINJA_COMMAND,
+
+  // For writing preformatted shell commands to Ninja files. This assumes the
+  // output already has the proper quoting and may include special shell
+  // characters which we want to pass to the shell (like when writing tool
+  // commands). Only Ninja "$" are escaped.
+  ESCAPE_NINJA_PREFORMATTED_COMMAND,
+
+  // Shell escaping as described by JSON Compilation Database spec:
+  // Parameters use shell quoting and shell escaping of quotes, with ‘"’ and ‘\’
+  // being the only special characters.
+  ESCAPE_COMPILATION_DATABASE,
+};
+
+enum EscapingPlatform {
+  // Do escaping for the current platform.
+  ESCAPE_PLATFORM_CURRENT,
+
+  // Force escaping for the given platform.
+  ESCAPE_PLATFORM_POSIX,
+  ESCAPE_PLATFORM_WIN,
+};
+
+struct EscapeOptions {
+  EscapingMode mode = ESCAPE_NONE;
+
+  // Controls how "fork" escaping is done. You will generally want to keep the
+  // default "current" platform.
+  EscapingPlatform platform = ESCAPE_PLATFORM_CURRENT;
+
+  // When the escaping mode is ESCAPE_SHELL, the escaper will normally put
+  // quotes around things with spaces. If this value is set to true, we'll
+  // disable the quoting feature and just add the spaces.
+  //
+  // This mode is for when quoting is done at some higher-level. Defaults to
+  // false. Note that Windows has strange behavior where the meaning of the
+  // backslashes changes according to if it is followed by a quote. The
+  // escaping rules assume that a double-quote will be appended to the result.
+  bool inhibit_quoting = false;
+};
+
+// Escapes the given input, returnining the result.
+//
+// If needed_quoting is non-null, whether the string was or should have been
+// (if inhibit_quoting was set) quoted will be written to it. This value should
+// be initialized to false by the caller and will be written to only if it's
+// true (the common use-case is for chaining calls).
+std::string EscapeString(const std::string_view& str,
+                         const EscapeOptions& options,
+                         bool* needed_quoting);
+
+// Same as EscapeString but writes the results to the given stream, saving a
+// copy.
+void EscapeStringToStream(std::ostream& out,
+                          const std::string_view& str,
+                          const EscapeOptions& options);
+
+// Same as EscapeString but escape JSON string and writes the results to the
+// given stream, saving a copy.
+void EscapeJSONStringToStream(std::ostream& out,
+                              const std::string_view& str,
+                              const EscapeOptions& options);
+
+#endif  // TOOLS_GN_ESCAPE_H_
diff --git a/src/gn/escape_unittest.cc b/src/gn/escape_unittest.cc
new file mode 100644 (file)
index 0000000..004498e
--- /dev/null
@@ -0,0 +1,112 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/escape.h"
+#include "gn/string_output_buffer.h"
+#include "util/test/test.h"
+
+TEST(Escape, Ninja) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA;
+  std::string result = EscapeString("asdf: \"$\\bar", opts, nullptr);
+  EXPECT_EQ("asdf$:$ \"$$\\bar", result);
+}
+
+TEST(Escape, Depfile) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_DEPFILE;
+  std::string result = EscapeString("asdf:$ \\#*[|]bar", opts, nullptr);
+  EXPECT_EQ("asdf:$$\\ \\\\\\#\\*\\[\\|\\]bar", result);
+}
+
+TEST(Escape, WindowsCommand) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  opts.platform = ESCAPE_PLATFORM_WIN;
+
+  // Regular string is passed, even if it has backslashes.
+  EXPECT_EQ("foo\\bar", EscapeString("foo\\bar", opts, nullptr));
+
+  // Spaces means the string is quoted, normal backslahes untouched.
+  bool needs_quoting = false;
+  EXPECT_EQ("\"foo\\$ bar\"", EscapeString("foo\\ bar", opts, &needs_quoting));
+  EXPECT_TRUE(needs_quoting);
+
+  // Inhibit quoting.
+  needs_quoting = false;
+  opts.inhibit_quoting = true;
+  EXPECT_EQ("foo\\$ bar", EscapeString("foo\\ bar", opts, &needs_quoting));
+  EXPECT_TRUE(needs_quoting);
+  opts.inhibit_quoting = false;
+
+  // Backslashes at the end of the string get escaped.
+  EXPECT_EQ("\"foo$ bar\\\\\\\\\"", EscapeString("foo bar\\\\", opts, nullptr));
+
+  // Backslashes preceding quotes are escaped, and the quote is escaped.
+  EXPECT_EQ("\"foo\\\\\\\"$ bar\"", EscapeString("foo\\\" bar", opts, nullptr));
+}
+
+TEST(Escape, PosixCommand) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  opts.platform = ESCAPE_PLATFORM_POSIX;
+
+  // : and $ ninja escaped with $. Then Shell-escape backslashes and quotes.
+  EXPECT_EQ("a$:\\$ \\\"\\$$\\\\b", EscapeString("a: \"$\\b", opts, nullptr));
+
+  // Some more generic shell chars.
+  EXPECT_EQ("a_\\;\\<\\*b", EscapeString("a_;<*b", opts, nullptr));
+
+  // Curly braces must be escaped to avoid brace expansion on systems using
+  // bash as default shell..
+  EXPECT_EQ("\\{a,b\\}\\{c,d\\}", EscapeString("{a,b}{c,d}", opts, nullptr));
+}
+
+TEST(Escape, NinjaPreformatted) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+
+  // Only $ is escaped.
+  EXPECT_EQ("a: \"$$\\b<;", EscapeString("a: \"$\\b<;", opts, nullptr));
+}
+
+TEST(Escape, Space) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_SPACE;
+
+  // ' ' is escaped.
+  EXPECT_EQ("-VERSION=\"libsrtp2\\ 2.1.0-pre\"",
+            EscapeString("-VERSION=\"libsrtp2 2.1.0-pre\"", opts, nullptr));
+}
+
+TEST(EscapeJSONString, NinjaPreformatted) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+  opts.inhibit_quoting = true;
+
+  StringOutputBuffer buffer;
+  std::ostream out(&buffer);
+
+  EscapeJSONStringToStream(out, "foo\\\" bar", opts);
+  EXPECT_EQ("foo\\\\\\\" bar", buffer.str());
+
+  StringOutputBuffer buffer1;
+  std::ostream out1(&buffer1);
+  EscapeJSONStringToStream(out1, "foo bar\\\\", opts);
+  EXPECT_EQ("foo bar\\\\\\\\", buffer1.str());
+
+  StringOutputBuffer buffer2;
+  std::ostream out2(&buffer2);
+  EscapeJSONStringToStream(out2, "a: \"$\\b", opts);
+  EXPECT_EQ("a: \\\"$$\\\\b", buffer2.str());
+}
+
+TEST(Escape, CompilationDatabase) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_COMPILATION_DATABASE;
+
+  // The only special characters are '"' and '\'.
+  std::string result = EscapeString("asdf:$ \\#*[|]bar", opts, nullptr);
+  EXPECT_EQ("\"asdf:$ \\\\#*[|]bar\"", result);
+}
diff --git a/src/gn/exec_process.cc b/src/gn/exec_process.cc
new file mode 100644 (file)
index 0000000..cc778d8
--- /dev/null
@@ -0,0 +1,289 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/exec_process.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_process_information.h"
+#include "base/win/win_util.h"
+#else
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "base/posix/file_descriptor_shuffle.h"
+#endif
+
+namespace internal {
+
+#if defined(OS_WIN)
+bool ExecProcess(const base::CommandLine& cmdline,
+                 const base::FilePath& startup_dir,
+                 std::string* std_out,
+                 std::string* std_err,
+                 int* exit_code) {
+  return ExecProcess(cmdline.GetCommandLineString(), startup_dir, std_out,
+                     std_err, exit_code);
+}
+
+bool ExecProcess(const std::u16string& cmdline_str,
+                 const base::FilePath& startup_dir,
+                 std::string* std_out,
+                 std::string* std_err,
+                 int* exit_code) {
+  SECURITY_ATTRIBUTES sa_attr;
+  // Set the bInheritHandle flag so pipe handles are inherited.
+  sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  sa_attr.bInheritHandle = TRUE;
+  sa_attr.lpSecurityDescriptor = nullptr;
+
+  // Create the pipe for the child process's STDOUT.
+  HANDLE out_read = nullptr;
+  HANDLE out_write = nullptr;
+  if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) {
+    NOTREACHED() << "Failed to create pipe";
+    return false;
+  }
+  base::win::ScopedHandle scoped_out_read(out_read);
+  base::win::ScopedHandle scoped_out_write(out_write);
+
+  // Create the pipe for the child process's STDERR.
+  HANDLE err_read = nullptr;
+  HANDLE err_write = nullptr;
+  if (!CreatePipe(&err_read, &err_write, &sa_attr, 0)) {
+    NOTREACHED() << "Failed to create pipe";
+    return false;
+  }
+  base::win::ScopedHandle scoped_err_read(err_read);
+  base::win::ScopedHandle scoped_err_write(err_write);
+
+  // Ensure the read handle to the pipe for STDOUT/STDERR is not inherited.
+  if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) {
+    NOTREACHED() << "Failed to disable pipe inheritance";
+    return false;
+  }
+  if (!SetHandleInformation(err_read, HANDLE_FLAG_INHERIT, 0)) {
+    NOTREACHED() << "Failed to disable pipe inheritance";
+    return false;
+  }
+
+  STARTUPINFO start_info = {};
+
+  start_info.cb = sizeof(STARTUPINFO);
+  start_info.hStdOutput = out_write;
+  // Keep the normal stdin.
+  start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+  // FIXME(brettw) set stderr here when we actually read it below.
+  // start_info.hStdError = err_write;
+  start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+  start_info.dwFlags |= STARTF_USESTDHANDLES;
+
+  std::u16string cmdline_writable = cmdline_str;
+
+  // Create the child process.
+  PROCESS_INFORMATION temp_process_info = {};
+  if (!CreateProcess(
+          nullptr, base::ToWCharT(&cmdline_writable[0]), nullptr, nullptr,
+          TRUE,  // Handles are inherited.
+          NORMAL_PRIORITY_CLASS, nullptr, base::ToWCharT(&startup_dir.value()),
+          &start_info, &temp_process_info)) {
+    return false;
+  }
+  base::win::ScopedProcessInformation proc_info(temp_process_info);
+
+  // Close our writing end of pipes now. Otherwise later read would not be
+  // able to detect end of child's output.
+  scoped_out_write.Close();
+  scoped_err_write.Close();
+
+  // Read output from the child process's pipe for STDOUT
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
+
+  // FIXME(brettw) read from stderr here! This is complicated because we want
+  // to read both of them at the same time, probably need overlapped I/O.
+  // Also uncomment start_info code above.
+  for (;;) {
+    DWORD bytes_read = 0;
+    BOOL success =
+        ReadFile(out_read, buffer, kBufferSize, &bytes_read, nullptr);
+    if (!success || bytes_read == 0)
+      break;
+    std_out->append(buffer, bytes_read);
+  }
+
+  // Let's wait for the process to finish.
+  WaitForSingleObject(proc_info.process_handle(), INFINITE);
+
+  DWORD dw_exit_code;
+  GetExitCodeProcess(proc_info.process_handle(), &dw_exit_code);
+  *exit_code = static_cast<int>(dw_exit_code);
+
+  return true;
+}
+#else
+// Reads from the provided file descriptor and appends to output. Returns false
+// if the fd is closed or there is an unexpected error (not
+// EINTR/EAGAIN/EWOULDBLOCK).
+bool ReadFromPipe(int fd, std::string* output) {
+  char buffer[256];
+  int bytes_read = HANDLE_EINTR(read(fd, buffer, sizeof(buffer)));
+  if (bytes_read == -1) {
+    return errno == EAGAIN || errno == EWOULDBLOCK;
+  } else if (bytes_read <= 0) {
+    return false;
+  }
+  output->append(buffer, bytes_read);
+  return true;
+}
+
+bool WaitForExit(int pid, int* exit_code) {
+  int status;
+  if (waitpid(pid, &status, 0) < 0) {
+    PLOG(ERROR) << "waitpid";
+    return false;
+  }
+
+  if (WIFEXITED(status)) {
+    *exit_code = WEXITSTATUS(status);
+    return true;
+  } else if (WIFSIGNALED(status)) {
+    if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM ||
+        WTERMSIG(status) == SIGHUP)
+      return false;
+  }
+  return false;
+}
+
+bool ExecProcess(const base::CommandLine& cmdline,
+                 const base::FilePath& startup_dir,
+                 std::string* std_out,
+                 std::string* std_err,
+                 int* exit_code) {
+  *exit_code = EXIT_FAILURE;
+
+  std::vector<std::string> argv = cmdline.argv();
+
+  int out_fd[2], err_fd[2];
+  pid_t pid;
+  base::InjectiveMultimap fd_shuffle1, fd_shuffle2;
+  std::unique_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]);
+
+  fd_shuffle1.reserve(3);
+  fd_shuffle2.reserve(3);
+
+  if (pipe(out_fd) < 0)
+    return false;
+  base::ScopedFD out_read(out_fd[0]), out_write(out_fd[1]);
+
+  if (pipe(err_fd) < 0)
+    return false;
+  base::ScopedFD err_read(err_fd[0]), err_write(err_fd[1]);
+
+  if (out_read.get() >= FD_SETSIZE || err_read.get() >= FD_SETSIZE)
+    return false;
+
+  switch (pid = fork()) {
+    case -1:  // error
+      return false;
+    case 0:  // child
+    {
+#if defined(OS_MAC)
+      // When debugging the app under Xcode, the child will receive a SIGTRAP
+      // signal which will terminate the child process. Ignore the signal to
+      // allow debugging under macOS.
+      sigignore(SIGTRAP);
+#endif
+
+      // DANGER: no calls to malloc are allowed from now on:
+      // http://crbug.com/36678
+      //
+      // STL iterators are also not allowed (including those implied
+      // by range-based for loops), since debug iterators use locks.
+
+      // Obscure fork() rule: in the child, if you don't end up doing exec*(),
+      // you call _exit() instead of exit(). This is because _exit() does not
+      // call any previously-registered (in the parent) exit handlers, which
+      // might do things like block waiting for threads that don't even exist
+      // in the child.
+      int dev_null = open("/dev/null", O_WRONLY);
+      if (dev_null < 0)
+        _exit(127);
+
+      fd_shuffle1.push_back(
+          base::InjectionArc(out_write.get(), STDOUT_FILENO, true));
+      fd_shuffle1.push_back(
+          base::InjectionArc(err_write.get(), STDERR_FILENO, true));
+      fd_shuffle1.push_back(base::InjectionArc(dev_null, STDIN_FILENO, true));
+      // Adding another element here? Remember to increase the argument to
+      // reserve(), above.
+
+      // DANGER: Do NOT convert to range-based for loop!
+      for (size_t i = 0; i < fd_shuffle1.size(); ++i)
+        fd_shuffle2.push_back(fd_shuffle1[i]);
+
+      if (!ShuffleFileDescriptors(&fd_shuffle1))
+        _exit(127);
+
+      base::SetCurrentDirectory(startup_dir);
+
+      // TODO(brettw) the base version GetAppOutput does a
+      // CloseSuperfluousFds call here. Do we need this?
+
+      // DANGER: Do NOT convert to range-based for loop!
+      for (size_t i = 0; i < argv.size(); i++)
+        argv_cstr[i] = const_cast<char*>(argv[i].c_str());
+      argv_cstr[argv.size()] = nullptr;
+      execvp(argv_cstr[0], argv_cstr.get());
+      _exit(127);
+    }
+    default:  // parent
+    {
+      // Close our writing end of pipe now. Otherwise later read would not
+      // be able to detect end of child's output (in theory we could still
+      // write to the pipe).
+      out_write.reset();
+      err_write.reset();
+
+      bool out_open = true, err_open = true;
+      while (out_open || err_open) {
+        fd_set read_fds;
+        FD_ZERO(&read_fds);
+        FD_SET(out_read.get(), &read_fds);
+        FD_SET(err_read.get(), &read_fds);
+        int res =
+            HANDLE_EINTR(select(std::max(out_read.get(), err_read.get()) + 1,
+                                &read_fds, nullptr, nullptr, nullptr));
+        if (res <= 0)
+          break;
+        if (FD_ISSET(out_read.get(), &read_fds))
+          out_open = ReadFromPipe(out_read.get(), std_out);
+        if (FD_ISSET(err_read.get(), &read_fds))
+          err_open = ReadFromPipe(err_read.get(), std_err);
+      }
+
+      return WaitForExit(pid, exit_code);
+    }
+  }
+
+  return false;
+}
+#endif
+
+}  // namespace internal
diff --git a/src/gn/exec_process.h b/src/gn/exec_process.h
new file mode 100644 (file)
index 0000000..91ee876
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_EXEC_PROCESS_H_
+#define TOOLS_GN_EXEC_PROCESS_H_
+
+#include <string>
+
+#include "util/build_config.h"
+
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
+namespace internal {
+
+bool ExecProcess(const base::CommandLine& cmdline,
+                 const base::FilePath& startup_dir,
+                 std::string* std_out,
+                 std::string* std_err,
+                 int* exit_code);
+
+#if defined(OS_WIN)
+bool ExecProcess(const std::u16string& cmdline_str,
+                 const base::FilePath& startup_dir,
+                 std::string* std_out,
+                 std::string* std_err,
+                 int* exit_code);
+#endif  // OS_WIN
+
+}  // namespace internal
+
+#endif  // TOOLS_GN_EXEC_PROCESS_H_
diff --git a/src/gn/exec_process_unittest.cc b/src/gn/exec_process_unittest.cc
new file mode 100644 (file)
index 0000000..599892d
--- /dev/null
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/exec_process.h"
+
+#include "base/command_line.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string_util.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+#if defined(OS_WIN)
+#include "base/strings/utf_string_conversions.h"
+#endif
+
+namespace internal {
+
+// TODO(cjhopman): Enable these tests when windows ExecProcess handles stderr.
+// 'python' is not runnable on Windows. Adding ["cmd", "/c"] fails because
+// CommandLine does unusual reordering of args.
+#if !defined(OS_WIN)
+namespace {
+bool ExecPython(const std::string& command,
+                std::string* std_out,
+                std::string* std_err,
+                int* exit_code) {
+  base::ScopedTempDir temp_dir;
+  CHECK(temp_dir.CreateUniqueTempDir());
+  base::CommandLine::StringVector args;
+#if defined(OS_WIN)
+  args.push_back(L"python");
+  args.push_back(L"-c");
+  args.push_back(base::UTF8ToUTF16(command));
+#else
+  args.push_back("python3");
+  args.push_back("-c");
+  args.push_back(command);
+#endif
+  return ExecProcess(base::CommandLine(args), temp_dir.GetPath(), std_out,
+                     std_err, exit_code);
+}
+}  // namespace
+
+TEST(ExecProcessTest, TestExitCode) {
+  std::string std_out, std_err;
+  int exit_code;
+
+  ASSERT_TRUE(
+      ExecPython("import sys; sys.exit(0)", &std_out, &std_err, &exit_code));
+  EXPECT_EQ(0, exit_code);
+
+  ASSERT_TRUE(
+      ExecPython("import sys; sys.exit(1)", &std_out, &std_err, &exit_code));
+  EXPECT_EQ(1, exit_code);
+
+  ASSERT_TRUE(
+      ExecPython("import sys; sys.exit(253)", &std_out, &std_err, &exit_code));
+  EXPECT_EQ(253, exit_code);
+
+  ASSERT_TRUE(ExecPython("throw Exception()", &std_out, &std_err, &exit_code));
+  EXPECT_EQ(1, exit_code);
+}
+
+// Test that large output is handled correctly. There are various ways that this
+// could potentially fail. For example, non-blocking Linux pipes have a 65536
+// byte buffer and, if stdout is non-blocking, python will throw an IOError when
+// a write exceeds the buffer size.
+TEST(ExecProcessTest, TestLargeOutput) {
+  std::string std_out, std_err;
+  int exit_code;
+
+  ASSERT_TRUE(ExecPython("import sys; print('o' * 1000000)", &std_out, &std_err,
+                         &exit_code));
+  EXPECT_EQ(0, exit_code);
+  EXPECT_EQ(1000001u, std_out.size());
+}
+
+TEST(ExecProcessTest, TestStdoutAndStderrOutput) {
+  std::string std_out, std_err;
+  int exit_code;
+
+  ASSERT_TRUE(
+      ExecPython("from __future__ import print_function; import sys; print('o' "
+                 "* 10000); print('e' * 10000, file=sys.stderr)",
+                 &std_out, &std_err, &exit_code));
+  EXPECT_EQ(0, exit_code);
+  EXPECT_EQ(10001u, std_out.size());
+  EXPECT_EQ(10001u, std_err.size());
+
+  std_out.clear();
+  std_err.clear();
+  ASSERT_TRUE(
+      ExecPython("from __future__ import print_function; import sys; print('e' "
+                 "* 10000, file=sys.stderr); print('o' * 10000)",
+                 &std_out, &std_err, &exit_code));
+  EXPECT_EQ(0, exit_code);
+  EXPECT_EQ(10001u, std_out.size());
+  EXPECT_EQ(10001u, std_err.size());
+}
+
+TEST(ExecProcessTest, TestOneOutputClosed) {
+  std::string std_out, std_err;
+  int exit_code;
+
+  ASSERT_TRUE(ExecPython("import sys; sys.stderr.close(); print('o' * 10000)",
+                         &std_out, &std_err, &exit_code));
+  EXPECT_EQ(0, exit_code);
+  EXPECT_EQ(10001u, std_out.size());
+  EXPECT_EQ(std_err.size(), 0u);
+
+  std_out.clear();
+  std_err.clear();
+  ASSERT_TRUE(
+      ExecPython("from __future__ import print_function; import sys; "
+                 "sys.stdout.close(); print('e' * 10000, file=sys.stderr)",
+                 &std_out, &std_err, &exit_code));
+  EXPECT_EQ(0, exit_code);
+  EXPECT_EQ(0u, std_out.size());
+  EXPECT_EQ(10001u, std_err.size());
+}
+#endif
+}  // namespace internal
diff --git a/src/gn/file_writer.cc b/src/gn/file_writer.cc
new file mode 100644 (file)
index 0000000..a4bb5be
--- /dev/null
@@ -0,0 +1,108 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/file_writer.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "gn/filesystem_utils.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/strings/utf_string_conversions.h"
+#else
+#include <fcntl.h>
+#include <unistd.h>
+#include "base/posix/eintr_wrapper.h"
+#endif
+
+FileWriter::~FileWriter() = default;
+
+#if defined(OS_WIN)
+
+bool FileWriter::Create(const base::FilePath& file_path) {
+  // On Windows, provide a custom implementation of base::WriteFile. Sometimes
+  // the base version fails, especially on the bots. The guess is that Windows
+  // Defender or other antivirus programs still have the file open (after
+  // checking for the read) when the write happens immediately after. This
+  // version opens with FILE_SHARE_READ (normally not what you want when
+  // replacing the entire contents of the file) which lets us continue even if
+  // another program has the file open for reading. See
+  // http://crbug.com/468437
+  file_path_ = base::UTF16ToUTF8(file_path.value());
+  file_ = base::win::ScopedHandle(::CreateFile(
+      reinterpret_cast<LPCWSTR>(file_path.value().c_str()), GENERIC_WRITE,
+      FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL));
+
+  valid_ = file_.IsValid();
+  if (!valid_) {
+    PLOG(ERROR) << "CreateFile failed for path " << file_path_;
+  }
+  return valid_;
+}
+
+bool FileWriter::Write(std::string_view str) {
+  if (!valid_)
+    return false;
+
+  DWORD written;
+  BOOL result =
+      ::WriteFile(file_.Get(), str.data(), str.size(), &written, nullptr);
+  if (!result) {
+    PLOG(ERROR) << "writing file " << file_path_ << " failed";
+    valid_ = false;
+    return false;
+  }
+  if (static_cast<size_t>(written) != str.size()) {
+    PLOG(ERROR) << "wrote " << written << " bytes to "
+                << file_path_ << " expected " << str.size();
+    valid_ = false;
+    return false;
+  }
+  return true;
+}
+
+bool FileWriter::Close() {
+  // NOTE: file_.Close() is not used here because it cannot return an error.
+  HANDLE handle = file_.Take();
+  if (handle && !::CloseHandle(handle))
+    return false;
+
+  return valid_;
+}
+
+#else  // !OS_WIN
+
+bool FileWriter::Create(const base::FilePath& file_path) {
+  fd_.reset(HANDLE_EINTR(::creat(file_path.value().c_str(), 0666)));
+  valid_ = fd_.is_valid();
+  if (!valid_) {
+    PLOG(ERROR) << "creat() failed for path " << file_path.value();
+  }
+  return valid_;
+}
+
+bool FileWriter::Write(std::string_view str) {
+  if (!valid_)
+    return false;
+
+  while (!str.empty()) {
+    ssize_t written = HANDLE_EINTR(::write(fd_.get(), str.data(), str.size()));
+    if (written <= 0) {
+      valid_ = false;
+      return false;
+    }
+    str.remove_prefix(static_cast<size_t>(written));
+  }
+  return true;
+}
+
+bool FileWriter::Close() {
+  // The ScopedFD reset() method will crash on EBADF and ignore other errors
+  // intentionally, so no need to check anything here.
+  fd_.reset();
+  return valid_;
+}
+
+#endif  // !OS_WIN
diff --git a/src/gn/file_writer.h b/src/gn/file_writer.h
new file mode 100644 (file)
index 0000000..0ae5061
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FILE_WRITER_H_
+#define TOOLS_GN_FILE_WRITER_H_
+
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#else
+#include "base/files/scoped_file.h"
+#endif
+
+#include <string>
+
+namespace base {
+class FilePath;
+}
+
+// Convenience class to write data to a file. This is used to work around two
+// limitations of base::WriteFile, i.e.:
+//
+//  - base::WriteFile() doesn't allow multiple writes to the target file.
+//
+//
+//  - Windows-specific issues created by anti-virus programs required opening
+//    the file differently (see http://crbug.com/468437).
+//
+// Usage is:
+//   1) Create instance.
+//   2) Call Create() to create the file.
+//   3) Call Write() one or more times to write data to it.
+//   4) Call Close(), or the destructor, to close the file.
+//
+// As soon as one method fails, all other calls will return false, this allows
+// simplified error checking as in:
+//
+//      FileWriter writer;
+//      writer.Create(<some_path>);
+//      writer.Write(<some_data>);
+//      writer.Write(<some_more_data>);
+//      if (!writer.Close()) {
+//         ... error happened in one of the above calls.
+//      }
+//
+class FileWriter {
+ public:
+  FileWriter() = default;
+  ~FileWriter();
+
+  // Create output file. Return true on success, false otherwise.
+  bool Create(const base::FilePath& file_path);
+
+  // Append |data| to the output file. Return true on success, or false on
+  // failure or if any previous Create() or Write() call failed.
+  bool Write(std::string_view data);
+
+  // Close the file. Return true on success, or false on failure or if
+  // any previous Create() or Write() call failed.
+  bool Close();
+
+ private:
+#if defined(OS_WIN)
+  base::win::ScopedHandle file_;
+  std::string file_path_;
+#else
+  base::ScopedFD fd_;
+#endif
+  bool valid_ = true;
+};
+
+#endif  // TOOLS_GN_FILE_WRITER_H_
diff --git a/src/gn/file_writer_unittest.cc b/src/gn/file_writer_unittest.cc
new file mode 100644 (file)
index 0000000..49f533a
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/file_writer.h"
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "gn/filesystem_utils.h"
+
+#include "util/test/test.h"
+
+TEST(FileWriter, SingleWrite) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  std::string data = "foo";
+
+  base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
+
+  FileWriter writer;
+  EXPECT_TRUE(writer.Create(file_path));
+  EXPECT_TRUE(writer.Write(data));
+  EXPECT_TRUE(writer.Close());
+
+  EXPECT_TRUE(ContentsEqual(file_path, data));
+}
+
+TEST(FileWriter, MultipleWrites) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  std::string data = "Hello World!";
+
+  base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
+
+  FileWriter writer;
+  EXPECT_TRUE(writer.Create(file_path));
+  EXPECT_TRUE(writer.Write("Hello "));
+  EXPECT_TRUE(writer.Write("World!"));
+  EXPECT_TRUE(writer.Close());
+
+  EXPECT_TRUE(ContentsEqual(file_path, data));
+}
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
new file mode 100644 (file)
index 0000000..9eb713b
--- /dev/null
@@ -0,0 +1,1077 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/filesystem_utils.h"
+
+#include <algorithm>
+
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/file_writer.h"
+#include "gn/location.h"
+#include "gn/settings.h"
+#include "gn/source_dir.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace {
+
+enum DotDisposition {
+  // The given dot is just part of a filename and is not special.
+  NOT_A_DIRECTORY,
+
+  // The given dot is the current directory.
+  DIRECTORY_CUR,
+
+  // The given dot is the first of a double dot that should take us up one.
+  DIRECTORY_UP
+};
+
+// When we find a dot, this function is called with the character following
+// that dot to see what it is. The return value indicates what type this dot is
+// (see above). This code handles the case where the dot is at the end of the
+// input.
+//
+// |*consumed_len| will contain the number of characters in the input that
+// express what we found.
+DotDisposition ClassifyAfterDot(const std::string& path,
+                                size_t after_dot,
+                                size_t* consumed_len) {
+  if (after_dot == path.size()) {
+    // Single dot at the end.
+    *consumed_len = 1;
+    return DIRECTORY_CUR;
+  }
+  if (IsSlash(path[after_dot])) {
+    // Single dot followed by a slash.
+    *consumed_len = 2;  // Consume the slash
+    return DIRECTORY_CUR;
+  }
+
+  if (path[after_dot] == '.') {
+    // Two dots.
+    if (after_dot + 1 == path.size()) {
+      // Double dot at the end.
+      *consumed_len = 2;
+      return DIRECTORY_UP;
+    }
+    if (IsSlash(path[after_dot + 1])) {
+      // Double dot folowed by a slash.
+      *consumed_len = 3;
+      return DIRECTORY_UP;
+    }
+  }
+
+  // The dots are followed by something else, not a directory.
+  *consumed_len = 1;
+  return NOT_A_DIRECTORY;
+}
+
+#if defined(OS_WIN)
+inline char NormalizeWindowsPathChar(char c) {
+  if (c == '/')
+    return '\\';
+  return base::ToLowerASCII(c);
+}
+
+// Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
+// paths.
+bool AreAbsoluteWindowsPathsEqual(const std::string_view& a,
+                                  const std::string_view& b) {
+  if (a.size() != b.size())
+    return false;
+
+  // For now, just do a case-insensitive ASCII comparison. We could convert to
+  // UTF-16 and use ICU if necessary.
+  for (size_t i = 0; i < a.size(); i++) {
+    if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i]))
+      return false;
+  }
+  return true;
+}
+
+bool DoesBeginWindowsDriveLetter(const std::string_view& path) {
+  if (path.size() < 3)
+    return false;
+
+  // Check colon first, this will generally fail fastest.
+  if (path[1] != ':')
+    return false;
+
+  // Check drive letter.
+  if (!base::IsAsciiAlpha(path[0]))
+    return false;
+
+  if (!IsSlash(path[2]))
+    return false;
+  return true;
+}
+#endif
+
+// A wrapper around FilePath.GetComponents that works the way we need. This is
+// not super efficient since it does some O(n) transformations on the path. If
+// this is called a lot, we might want to optimize.
+std::vector<base::FilePath::StringType> GetPathComponents(
+    const base::FilePath& path) {
+  std::vector<base::FilePath::StringType> result;
+  path.GetComponents(&result);
+
+  if (result.empty())
+    return result;
+
+  // GetComponents will preserve the "/" at the beginning, which confuses us.
+  // We don't expect to have relative paths in this function.
+  // Don't use IsSeparator since we always want to allow backslashes.
+  if (result[0] == FILE_PATH_LITERAL("/") ||
+      result[0] == FILE_PATH_LITERAL("\\"))
+    result.erase(result.begin());
+
+#if defined(OS_WIN)
+  // On Windows, GetComponents will give us [ "C:", "/", "foo" ], and we
+  // don't want the slash in there. This doesn't support input like "C:foo"
+  // which means foo relative to the current directory of the C drive but
+  // that's basically legacy DOS behavior we don't need to support.
+  if (result.size() >= 2 && result[1].size() == 1 &&
+      IsSlash(static_cast<char>(result[1][0])))
+    result.erase(result.begin() + 1);
+#endif
+
+  return result;
+}
+
+// Provides the equivalent of == for filesystem strings, trying to do
+// approximately the right thing with case.
+bool FilesystemStringsEqual(const base::FilePath::StringType& a,
+                            const base::FilePath::StringType& b) {
+#if defined(OS_WIN)
+  // Assume case-insensitive filesystems on Windows. We use the CompareString
+  // function to do a case-insensitive comparison based on the current locale
+  // (we don't want GN to depend on ICU which is large and requires data
+  // files). This isn't perfect, but getting this perfectly right is very
+  // difficult and requires I/O, and this comparison should cover 99.9999% of
+  // all cases.
+  //
+  // Note: The documentation for CompareString says it runs fastest on
+  // null-terminated strings with -1 passed for the length, so we do that here.
+  // There should not be embedded nulls in filesystem strings.
+  return ::CompareString(LOCALE_USER_DEFAULT, LINGUISTIC_IGNORECASE,
+                         reinterpret_cast<LPCWSTR>(a.c_str()), -1,
+                         reinterpret_cast<LPCWSTR>(b.c_str()),
+                         -1) == CSTR_EQUAL;
+#else
+  // Assume case-sensitive filesystems on non-Windows.
+  return a == b;
+#endif
+}
+
+// Helper function for computing subdirectories in the build directory
+// corresponding to absolute paths. This will try to resolve the absolute
+// path as a source-relative path first, and otherwise it creates a
+// special subdirectory for absolute paths to keep them from colliding with
+// other generated sources and outputs.
+void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
+                                   const SourceDir& source_dir,
+                                   OutputFile* result) {
+  const std::string& build_dir = build_settings->build_dir().value();
+
+  if (base::StartsWith(source_dir.value(), build_dir,
+                       base::CompareCase::SENSITIVE)) {
+    size_t build_dir_size = build_dir.size();
+    result->value().append(&source_dir.value()[build_dir_size],
+                           source_dir.value().size() - build_dir_size);
+  } else {
+    result->value().append("ABS_PATH");
+#if defined(OS_WIN)
+    // Windows absolute path contains ':' after drive letter. Remove it to
+    // avoid inserting ':' in the middle of path (eg. "ABS_PATH/C:/").
+    std::string src_dir_value = source_dir.value();
+    const auto colon_pos = src_dir_value.find(':');
+    if (colon_pos != std::string::npos)
+      src_dir_value.erase(src_dir_value.begin() + colon_pos);
+#else
+    const std::string& src_dir_value = source_dir.value();
+#endif
+    result->value().append(src_dir_value);
+  }
+}
+
+size_t AbsPathLenWithNoTrailingSlash(const std::string_view& path) {
+  size_t len = path.size();
+#if defined(OS_WIN)
+  size_t min_len = 3;
+#else
+  // On posix system. The minimal abs path is "/".
+  size_t min_len = 1;
+#endif
+  for (; len > min_len && IsSlash(path[len - 1]); len--)
+    ;
+  return len;
+}
+}  // namespace
+
+std::string FilePathToUTF8(const base::FilePath::StringType& str) {
+#if defined(OS_WIN)
+  return base::UTF16ToUTF8(str);
+#else
+  return str;
+#endif
+}
+
+base::FilePath UTF8ToFilePath(const std::string_view& sp) {
+#if defined(OS_WIN)
+  return base::FilePath(base::UTF8ToUTF16(sp));
+#else
+  return base::FilePath(sp);
+#endif
+}
+
+size_t FindExtensionOffset(const std::string& path) {
+  for (int i = static_cast<int>(path.size()); i >= 0; i--) {
+    if (IsSlash(path[i]))
+      break;
+    if (path[i] == '.')
+      return i + 1;
+  }
+  return std::string::npos;
+}
+
+std::string_view FindExtension(const std::string* path) {
+  size_t extension_offset = FindExtensionOffset(*path);
+  if (extension_offset == std::string::npos)
+    return std::string_view();
+  return std::string_view(&path->data()[extension_offset],
+                          path->size() - extension_offset);
+}
+
+size_t FindFilenameOffset(const std::string& path) {
+  for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
+    if (IsSlash(path[i]))
+      return i + 1;
+  }
+  return 0;  // No filename found means everything was the filename.
+}
+
+std::string_view FindFilename(const std::string* path) {
+  size_t filename_offset = FindFilenameOffset(*path);
+  if (filename_offset == 0)
+    return std::string_view(*path);  // Everything is the file name.
+  return std::string_view(&(*path).data()[filename_offset],
+                          path->size() - filename_offset);
+}
+
+std::string_view FindFilenameNoExtension(const std::string* path) {
+  if (path->empty())
+    return std::string_view();
+  size_t filename_offset = FindFilenameOffset(*path);
+  size_t extension_offset = FindExtensionOffset(*path);
+
+  size_t name_len;
+  if (extension_offset == std::string::npos)
+    name_len = path->size() - filename_offset;
+  else
+    name_len = extension_offset - filename_offset - 1;
+
+  return std::string_view(&(*path).data()[filename_offset], name_len);
+}
+
+void RemoveFilename(std::string* path) {
+  path->resize(FindFilenameOffset(*path));
+}
+
+bool EndsWithSlash(const std::string_view s) {
+  return !s.empty() && IsSlash(s[s.size() - 1]);
+}
+
+std::string_view FindDir(const std::string* path) {
+  size_t filename_offset = FindFilenameOffset(*path);
+  if (filename_offset == 0u)
+    return std::string_view();
+  return std::string_view(path->data(), filename_offset);
+}
+
+std::string_view FindLastDirComponent(const SourceDir& dir) {
+  const std::string& dir_string = dir.value();
+
+  if (dir_string.empty())
+    return std::string_view();
+  int cur = static_cast<int>(dir_string.size()) - 1;
+  DCHECK(dir_string[cur] == '/');
+  int end = cur;
+  cur--;  // Skip before the last slash.
+
+  for (; cur >= 0; cur--) {
+    if (dir_string[cur] == '/')
+      return std::string_view(&dir_string[cur + 1], end - cur - 1);
+  }
+  return std::string_view(&dir_string[0], end);
+}
+
+bool IsStringInOutputDir(const SourceDir& output_dir, const std::string& str) {
+  // This check will be wrong for all proper prefixes "e.g. "/output" will
+  // match "/out" but we don't really care since this is just a sanity check.
+  const std::string& dir_str = output_dir.value();
+  return str.compare(0, dir_str.length(), dir_str) == 0;
+}
+
+bool EnsureStringIsInOutputDir(const SourceDir& output_dir,
+                               const std::string& str,
+                               const ParseNode* origin,
+                               Err* err) {
+  if (IsStringInOutputDir(output_dir, str))
+    return true;  // Output directory is hardcoded.
+
+  *err = Err(
+      origin, "File is not inside output directory.",
+      "The given file should be in the output directory. Normally you would "
+      "specify\n\"$target_out_dir/foo\" or "
+      "\"$target_gen_dir/foo\". I interpreted this as\n\"" +
+          str + "\".");
+  return false;
+}
+
+bool IsPathAbsolute(const std::string_view& path) {
+  if (path.empty())
+    return false;
+
+  if (!IsSlash(path[0])) {
+#if defined(OS_WIN)
+    // Check for Windows system paths like "C:\foo".
+    if (path.size() > 2 && path[1] == ':' && IsSlash(path[2]))
+      return true;
+#endif
+    return false;  // Doesn't begin with a slash, is relative.
+  }
+
+  // Double forward slash at the beginning means source-relative (we don't
+  // allow backslashes for denoting this).
+  if (path.size() > 1 && path[1] == '/')
+    return false;
+
+  return true;
+}
+
+bool IsPathSourceAbsolute(const std::string_view& path) {
+  return (path.size() >= 2 && path[0] == '/' && path[1] == '/');
+}
+
+bool MakeAbsolutePathRelativeIfPossible(const std::string_view& source_root,
+                                        const std::string_view& path,
+                                        std::string* dest) {
+  DCHECK(IsPathAbsolute(source_root));
+  DCHECK(IsPathAbsolute(path));
+
+  dest->clear();
+
+  // There is no specification of how many slashes may be at the end of
+  // source_root or path. Trim them off for easier string manipulation.
+  size_t path_len = AbsPathLenWithNoTrailingSlash(path);
+  size_t source_root_len = AbsPathLenWithNoTrailingSlash(source_root);
+
+  if (source_root_len > path_len)
+    return false;  // The source root is longer: the path can never be inside.
+#if defined(OS_WIN)
+  // Source root should be canonical on Windows. Note that the initial slash
+  // must be forward slash, but that the other ones can be either forward or
+  // backward.
+  DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
+         source_root[1] == ':' && IsSlash(source_root[2]));
+
+  size_t after_common_index = std::string::npos;
+  if (DoesBeginWindowsDriveLetter(path)) {
+    // Handle "C:\foo"
+    if (AreAbsoluteWindowsPathsEqual(source_root.substr(0, source_root_len),
+                                     path.substr(0, source_root_len))) {
+      after_common_index = source_root_len;
+      if (path_len == source_root_len) {
+        *dest = "//";
+        return true;
+      }
+    } else {
+      return false;
+    }
+  } else if (path[0] == '/' && source_root_len <= path_len - 1 &&
+             DoesBeginWindowsDriveLetter(path.substr(1))) {
+    // Handle "/C:/foo"
+    if (AreAbsoluteWindowsPathsEqual(source_root.substr(0, source_root_len),
+                                     path.substr(1, source_root_len))) {
+      after_common_index = source_root_len + 1;
+      if (path_len + 1 == source_root_len) {
+        *dest = "//";
+        return true;
+      }
+    } else {
+      return false;
+    }
+  } else {
+    return false;
+  }
+
+  // If we get here, there's a match and after_common_index identifies the
+  // part after it.
+
+  if (!IsSlash(path[after_common_index])) {
+    // path is ${source-root}SUFFIX/...
+    return false;
+  }
+  // A source-root relative path, The input may have an unknown number of
+  // slashes after the previous match. Skip over them.
+  size_t first_after_slash = after_common_index + 1;
+  while (first_after_slash < path_len && IsSlash(path[first_after_slash]))
+    first_after_slash++;
+  dest->assign("//");  // Result is source root relative.
+  dest->append(&path.data()[first_after_slash],
+               path.size() - first_after_slash);
+  return true;
+
+#else
+
+  // On non-Windows this is easy. Since we know both are absolute, just do a
+  // prefix check.
+
+  if (path.substr(0, source_root_len) ==
+      source_root.substr(0, source_root_len)) {
+    if (path_len == source_root_len) {
+      // path is equivalent to source_root.
+      *dest = "//";
+      return true;
+    } else if (!IsSlash(path[source_root_len])) {
+      // path is ${source-root}SUFFIX/...
+      return false;
+    }
+    // A source-root relative path, The input may have an unknown number of
+    // slashes after the previous match. Skip over them.
+    size_t first_after_slash = source_root_len + 1;
+    while (first_after_slash < path_len && IsSlash(path[first_after_slash]))
+      first_after_slash++;
+
+    dest->assign("//");  // Result is source root relative.
+    dest->append(&path.data()[first_after_slash],
+                 path.size() - first_after_slash);
+    return true;
+  }
+  return false;
+#endif
+}
+
+base::FilePath MakeAbsoluteFilePathRelativeIfPossible(
+    const base::FilePath& base,
+    const base::FilePath& target) {
+  DCHECK(base.IsAbsolute());
+  DCHECK(target.IsAbsolute());
+  std::vector<base::FilePath::StringType> base_components;
+  std::vector<base::FilePath::StringType> target_components;
+  base.GetComponents(&base_components);
+  target.GetComponents(&target_components);
+#if defined(OS_WIN)
+  // On Windows, it's impossible to have a relative path from C:\foo to D:\bar,
+  // so return the target as an absolute path instead.
+  if (base_components[0] != target_components[0])
+    return target;
+
+  // GetComponents() returns the first slash after the root. Set it to the
+  // same value in both component lists so that relative paths between
+  // "C:/foo/..." and "C:\foo\..." are computed correctly.
+  target_components[1] = base_components[1];
+#endif
+  size_t i;
+  for (i = 0; i < base_components.size() && i < target_components.size(); i++) {
+    if (base_components[i] != target_components[i])
+      break;
+  }
+  std::vector<base::FilePath::StringType> relative_components;
+  for (size_t j = i; j < base_components.size(); j++)
+    relative_components.push_back(base::FilePath::kParentDirectory);
+  for (size_t j = i; j < target_components.size(); j++)
+    relative_components.push_back(target_components[j]);
+  if (relative_components.size() <= 1) {
+    // In case the file pointed-to is an executable, prepend the current
+    // directory to the path -- if the path was "gn", use "./gn" instead.  If
+    // the file path is used in a command, this prevents issues where "gn" might
+    // not be in the PATH (or it is in the path, and the wrong gn is used).
+    relative_components.insert(relative_components.begin(),
+                               base::FilePath::kCurrentDirectory);
+  }
+  // base::FilePath::Append(component) replaces the file path with |component|
+  // if the path is base::Filepath::kCurrentDirectory.  We want to preserve the
+  // leading "./", so we build the path ourselves and use that to construct the
+  // base::FilePath.
+  base::FilePath::StringType separator(&base::FilePath::kSeparators[0], 1);
+  return base::FilePath(base::JoinString(relative_components, separator));
+}
+
+void NormalizePath(std::string* path, const std::string_view& source_root) {
+  char* pathbuf = path->empty() ? nullptr : &(*path)[0];
+
+  // top_index is the first character we can modify in the path. Anything
+  // before this indicates where the path is relative to.
+  size_t top_index = 0;
+  bool is_relative = true;
+  if (!path->empty() && pathbuf[0] == '/') {
+    is_relative = false;
+
+    if (path->size() > 1 && pathbuf[1] == '/') {
+      // Two leading slashes, this is a path into the source dir.
+      top_index = 2;
+    } else {
+      // One leading slash, this is a system-absolute path.
+      top_index = 1;
+    }
+  }
+
+  size_t dest_i = top_index;
+  for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
+    if (pathbuf[src_i] == '.') {
+      if (src_i == 0 || IsSlash(pathbuf[src_i - 1])) {
+        // Slash followed by a dot, see if it's something special.
+        size_t consumed_len;
+        switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
+          case NOT_A_DIRECTORY:
+            // Copy the dot to the output, it means nothing special.
+            pathbuf[dest_i++] = pathbuf[src_i++];
+            break;
+          case DIRECTORY_CUR:
+            // Current directory, just skip the input.
+            src_i += consumed_len;
+            break;
+          case DIRECTORY_UP:
+            // Back up over previous directory component. If we're already
+            // at the top, preserve the "..".
+            if (dest_i > top_index) {
+              // The previous char was a slash, remove it.
+              dest_i--;
+            }
+
+            if (dest_i == top_index) {
+              if (is_relative) {
+                // We're already at the beginning of a relative input, copy the
+                // ".." and continue. We need the trailing slash if there was
+                // one before (otherwise we're at the end of the input).
+                pathbuf[dest_i++] = '.';
+                pathbuf[dest_i++] = '.';
+                if (consumed_len == 3)
+                  pathbuf[dest_i++] = '/';
+
+                // This also makes a new "root" that we can't delete by going
+                // up more levels.  Otherwise "../.." would collapse to
+                // nothing.
+                top_index = dest_i;
+              } else if (top_index == 2 && !source_root.empty()) {
+                // |path| was passed in as a source-absolute path. Prepend
+                // |source_root| to make |path| absolute. |source_root| must not
+                // end with a slash unless we are at root.
+                DCHECK(source_root.size() == 1u ||
+                       !IsSlash(source_root[source_root.size() - 1u]));
+                size_t source_root_len = source_root.size();
+
+#if defined(OS_WIN)
+                // On Windows, if the source_root does not start with a slash,
+                // append one here for consistency.
+                if (!IsSlash(source_root[0])) {
+                  path->insert(0, "/" + std::string(source_root));
+                  source_root_len++;
+                } else {
+                  path->insert(0, source_root.data(), source_root_len);
+                }
+
+                // Normalize slashes in source root portion.
+                for (size_t i = 0; i < source_root_len; ++i) {
+                  if ((*path)[i] == '\\')
+                    (*path)[i] = '/';
+                }
+#else
+                path->insert(0, source_root.data(), source_root_len);
+#endif
+
+                // |path| is now absolute, so |top_index| is 1. |dest_i| and
+                // |src_i| should be incremented to keep the same relative
+                // position. Consume the leading "//" by decrementing |dest_i|.
+                top_index = 1;
+                pathbuf = &(*path)[0];
+                dest_i += source_root_len - 2;
+                src_i += source_root_len;
+
+                // Just find the previous slash or the beginning of input.
+                while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
+                  dest_i--;
+              }
+              // Otherwise we're at the beginning of a system-absolute path, or
+              // a source-absolute path for which we don't know the absolute
+              // path. Don't allow ".." to go up another level, and just eat it.
+            } else {
+              // Just find the previous slash or the beginning of input.
+              while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
+                dest_i--;
+            }
+            src_i += consumed_len;
+        }
+      } else {
+        // Dot not preceded by a slash, copy it literally.
+        pathbuf[dest_i++] = pathbuf[src_i++];
+      }
+    } else if (IsSlash(pathbuf[src_i])) {
+      if (src_i > 0 && IsSlash(pathbuf[src_i - 1])) {
+        // Two slashes in a row, skip over it.
+        src_i++;
+      } else {
+        // Just one slash, copy it, normalizing to forward slash.
+        pathbuf[dest_i] = '/';
+        dest_i++;
+        src_i++;
+      }
+    } else {
+      // Input nothing special, just copy it.
+      pathbuf[dest_i++] = pathbuf[src_i++];
+    }
+  }
+  path->resize(dest_i);
+}
+
+void ConvertPathToSystem(std::string* path) {
+#if defined(OS_WIN)
+  for (size_t i = 0; i < path->size(); i++) {
+    if ((*path)[i] == '/')
+      (*path)[i] = '\\';
+  }
+#endif
+}
+
+std::string MakeRelativePath(const std::string& input,
+                             const std::string& dest) {
+#if defined(OS_WIN)
+  // Make sure that absolute |input| path starts with a slash if |dest| path
+  // does. Otherwise skipping common prefixes won't work properly. Ensure the
+  // same for |dest| path too.
+  if (IsPathAbsolute(input) && !IsSlash(input[0]) && IsSlash(dest[0])) {
+    std::string corrected_input(1, dest[0]);
+    corrected_input.append(input);
+    return MakeRelativePath(corrected_input, dest);
+  }
+  if (IsPathAbsolute(dest) && !IsSlash(dest[0]) && IsSlash(input[0])) {
+    std::string corrected_dest(1, input[0]);
+    corrected_dest.append(dest);
+    return MakeRelativePath(input, corrected_dest);
+  }
+
+  // Make sure that both absolute paths use the same drive letter case.
+  if (IsPathAbsolute(input) && IsPathAbsolute(dest) && input.size() > 1 &&
+      dest.size() > 1) {
+    int letter_pos = base::IsAsciiAlpha(input[0]) ? 0 : 1;
+    if (input[letter_pos] != dest[letter_pos] &&
+        base::ToUpperASCII(input[letter_pos]) ==
+            base::ToUpperASCII(dest[letter_pos])) {
+      std::string corrected_input = input;
+      corrected_input[letter_pos] = dest[letter_pos];
+      return MakeRelativePath(corrected_input, dest);
+    }
+  }
+#endif
+
+  DCHECK(EndsWithSlash(dest));
+  std::string ret;
+
+  // Skip the common prefixes of the source and dest as long as they end in
+  // a [back]slash or end the string. dest always ends with a (back)slash in
+  // this function, so checking dest for just that is sufficient.
+  size_t common_prefix_len = 0;
+  size_t max_common_length = std::min(input.size(), dest.size());
+  for (size_t i = common_prefix_len; i <= max_common_length; i++) {
+    if ((IsSlash(input[i]) || input[i] == '\0') && IsSlash(dest[i]))
+      common_prefix_len = i + 1;
+    else if (input[i] != dest[i])
+      break;
+  }
+
+  // Invert the dest dir starting from the end of the common prefix.
+  for (size_t i = common_prefix_len; i < dest.size(); i++) {
+    if (IsSlash(dest[i]))
+      ret.append("../");
+  }
+
+  // Append any remaining unique input.
+  if (common_prefix_len <= input.size())
+    ret.append(&input[common_prefix_len], input.size() - common_prefix_len);
+  else if (input.back() != '/' && !ret.empty())
+    ret.pop_back();
+
+  // If the result is still empty, the paths are the same.
+  if (ret.empty())
+    ret.push_back('.');
+
+  return ret;
+}
+
+std::string RebasePath(const std::string& input,
+                       const SourceDir& dest_dir,
+                       const std::string_view& source_root) {
+  std::string ret;
+  DCHECK(source_root.empty() ||
+         !base::EndsWith(source_root, "/", base::CompareCase::SENSITIVE));
+
+  bool input_is_source_path =
+      (input.size() >= 2 && input[0] == '/' && input[1] == '/');
+
+  if (!source_root.empty() &&
+      (!input_is_source_path || !dest_dir.is_source_absolute())) {
+    std::string input_full;
+    std::string dest_full;
+    if (input_is_source_path) {
+      input_full.append(source_root);
+      input_full.push_back('/');
+      input_full.append(input, 2, std::string::npos);
+    } else {
+      input_full.append(input);
+    }
+    if (dest_dir.is_source_absolute()) {
+      dest_full.append(source_root);
+      dest_full.push_back('/');
+      dest_full.append(dest_dir.value(), 2, std::string::npos);
+    } else {
+#if defined(OS_WIN)
+      // On Windows, SourceDir system-absolute paths start
+      // with /, e.g. "/C:/foo/bar".
+      const std::string& value = dest_dir.value();
+      if (value.size() > 2 && value[2] == ':')
+        dest_full.append(dest_dir.value().substr(1));
+      else
+        dest_full.append(dest_dir.value());
+#else
+      dest_full.append(dest_dir.value());
+#endif
+    }
+    bool remove_slash = false;
+    if (!EndsWithSlash(input_full)) {
+      input_full.push_back('/');
+      remove_slash = true;
+    }
+    ret = MakeRelativePath(input_full, dest_full);
+    if (remove_slash && ret.size() > 1)
+      ret.pop_back();
+    return ret;
+  }
+
+  ret = MakeRelativePath(input, dest_dir.value());
+  return ret;
+}
+
+base::FilePath ResolvePath(const std::string& value,
+                           bool as_file,
+                           const base::FilePath& source_root) {
+  if (value.empty())
+    return base::FilePath();
+
+  std::string converted;
+  if (!IsPathSourceAbsolute(value)) {
+    if (value.size() > 2 && value[2] == ':') {
+      // Windows path, strip the leading slash.
+      converted.assign(&value[1], value.size() - 1);
+    } else {
+      converted.assign(value);
+    }
+    return base::FilePath(UTF8ToFilePath(converted));
+  }
+
+  // String the double-leading slash for source-relative paths.
+  converted.assign(&value[2], value.size() - 2);
+
+  if (as_file && source_root.empty())
+    return UTF8ToFilePath(converted).NormalizePathSeparatorsTo('/');
+
+  return source_root.Append(UTF8ToFilePath(converted))
+      .NormalizePathSeparatorsTo('/');
+}
+
+template <typename StringType>
+std::string ResolveRelative(const StringType& input,
+                            const std::string& value,
+                            bool as_file,
+                            const std::string_view& source_root) {
+  std::string result;
+
+  if (input.size() >= 2 && input[0] == '/' && input[1] == '/') {
+    // Source-relative.
+    result.assign(input.data(), input.size());
+    if (!as_file) {
+      if (!EndsWithSlash(result))
+        result.push_back('/');
+    }
+    NormalizePath(&result, source_root);
+    return result;
+  } else if (IsPathAbsolute(input)) {
+    if (source_root.empty() ||
+        !MakeAbsolutePathRelativeIfPossible(source_root, input, &result)) {
+#if defined(OS_WIN)
+      if (input[0] != '/')  // See the file case for why we do this check.
+        result = "/";
+#endif
+      result.append(input.data(), input.size());
+    }
+    NormalizePath(&result);
+    if (!as_file) {
+      if (!EndsWithSlash(result))
+        result.push_back('/');
+    }
+    return result;
+  }
+
+  if (!source_root.empty()) {
+    std::string absolute =
+        FilePathToUTF8(ResolvePath(value, as_file, UTF8ToFilePath(source_root))
+                           .AppendASCII(input)
+                           .value());
+    NormalizePath(&absolute);
+    if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &result)) {
+#if defined(OS_WIN)
+      if (absolute[0] != '/')  // See the file case for why we do this check.
+        result = "/";
+#endif
+      result.append(absolute.data(), absolute.size());
+    }
+    if (!as_file && !EndsWithSlash(result))
+      result.push_back('/');
+    return result;
+  }
+
+  // With no source_root, there's nothing we can do about
+  // e.g. input=../../../path/to/file and value=//source and we'll
+  // erroneously return //file.
+  result.reserve(value.size() + input.size());
+  result.assign(value);
+  result.append(input.data(), input.size());
+
+  NormalizePath(&result);
+  if (!as_file && !EndsWithSlash(result))
+    result.push_back('/');
+
+  return result;
+}
+
+// Explicit template instantiation
+template std::string ResolveRelative(const std::string_view& input,
+                                     const std::string& value,
+                                     bool as_file,
+                                     const std::string_view& source_root);
+
+template std::string ResolveRelative(const std::string& input,
+                                     const std::string& value,
+                                     bool as_file,
+                                     const std::string_view& source_root);
+
+std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
+  std::string ret;
+
+  if (dir.value().empty()) {
+    // Just keep input the same.
+  } else if (dir.value() == "/") {
+    ret.assign("/.");
+  } else if (dir.value() == "//") {
+    ret.assign("//.");
+  } else {
+    ret.assign(dir.value());
+    ret.resize(ret.size() - 1);
+  }
+  return ret;
+}
+
+SourceDir SourceDirForPath(const base::FilePath& source_root,
+                           const base::FilePath& path) {
+  std::vector<base::FilePath::StringType> source_comp =
+      GetPathComponents(source_root);
+  std::vector<base::FilePath::StringType> path_comp = GetPathComponents(path);
+
+  // See if path is inside the source root by looking for each of source root's
+  // components at the beginning of path.
+  bool is_inside_source;
+  if (path_comp.size() < source_comp.size() || source_root.empty()) {
+    // Too small to fit.
+    is_inside_source = false;
+  } else {
+    is_inside_source = true;
+    for (size_t i = 0; i < source_comp.size(); i++) {
+      if (!FilesystemStringsEqual(source_comp[i], path_comp[i])) {
+        is_inside_source = false;
+        break;
+      }
+    }
+  }
+
+  std::string result_str;
+  size_t initial_path_comp_to_use;
+  if (is_inside_source) {
+    // Construct a source-relative path beginning in // and skip all of the
+    // shared directories.
+    result_str = "//";
+    initial_path_comp_to_use = source_comp.size();
+  } else {
+    // Not inside source code, construct a system-absolute path.
+    result_str = "/";
+    initial_path_comp_to_use = 0;
+  }
+
+  for (size_t i = initial_path_comp_to_use; i < path_comp.size(); i++) {
+    result_str.append(FilePathToUTF8(path_comp[i]));
+    result_str.push_back('/');
+  }
+  return SourceDir(std::move(result_str));
+}
+
+SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root) {
+  base::FilePath cd;
+  base::GetCurrentDirectory(&cd);
+  return SourceDirForPath(source_root, cd);
+}
+
+std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) {
+  // The default toolchain has no subdir.
+  if (is_default)
+    return std::string();
+
+  // For now just assume the toolchain name is always a valid dir name. We may
+  // want to clean up the in the future.
+  return toolchain_label.name() + "/";
+}
+
+bool ContentsEqual(const base::FilePath& file_path, const std::string& data) {
+  // Compare file and stream sizes first. Quick and will save us some time if
+  // they are different sizes.
+  int64_t file_size;
+  if (!base::GetFileSize(file_path, &file_size) ||
+      static_cast<size_t>(file_size) != data.size()) {
+    return false;
+  }
+
+  std::string file_data;
+  file_data.resize(file_size);
+  if (!base::ReadFileToString(file_path, &file_data))
+    return false;
+
+  return file_data == data;
+}
+
+bool WriteFileIfChanged(const base::FilePath& file_path,
+                        const std::string& data,
+                        Err* err) {
+  if (ContentsEqual(file_path, data))
+    return true;
+
+  return WriteFile(file_path, data, err);
+}
+
+bool WriteFile(const base::FilePath& file_path,
+               const std::string& data,
+               Err* err) {
+  // Create the directory if necessary.
+  if (!base::CreateDirectory(file_path.DirName())) {
+    if (err) {
+      *err =
+          Err(Location(), "Unable to create directory.",
+              "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
+    }
+    return false;
+  }
+
+  FileWriter writer;
+  writer.Create(file_path);
+  writer.Write(data);
+  bool write_success = writer.Close();
+
+  if (!write_success && err) {
+    *err = Err(Location(), "Unable to write file.",
+               "I was writing \"" + FilePathToUTF8(file_path) + "\".");
+  }
+
+  return write_success;
+}
+
+BuildDirContext::BuildDirContext(const Target* target)
+    : BuildDirContext(target->settings()) {}
+
+BuildDirContext::BuildDirContext(const Settings* settings)
+    : BuildDirContext(settings->build_settings(),
+                      settings->toolchain_label(),
+                      settings->is_default()) {}
+
+BuildDirContext::BuildDirContext(const Scope* execution_scope)
+    : BuildDirContext(execution_scope->settings()) {}
+
+BuildDirContext::BuildDirContext(const Scope* execution_scope,
+                                 const Label& toolchain_label)
+    : BuildDirContext(execution_scope->settings()->build_settings(),
+                      toolchain_label,
+                      execution_scope->settings()->default_toolchain_label() ==
+                          toolchain_label) {}
+
+BuildDirContext::BuildDirContext(const BuildSettings* in_build_settings,
+                                 const Label& in_toolchain_label,
+                                 bool in_is_default_toolchain)
+    : build_settings(in_build_settings),
+      toolchain_label(in_toolchain_label),
+      is_default_toolchain(in_is_default_toolchain) {}
+
+SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
+                                 BuildDirType type) {
+  return GetBuildDirAsOutputFile(context, type)
+      .AsSourceDir(context.build_settings);
+}
+
+OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
+                                   BuildDirType type) {
+  OutputFile result(GetOutputSubdirName(context.toolchain_label,
+                                        context.is_default_toolchain));
+  DCHECK(result.value().empty() || result.value().back() == '/');
+
+  if (type == BuildDirType::GEN)
+    result.value().append("gen/");
+  else if (type == BuildDirType::OBJ)
+    result.value().append("obj/");
+  return result;
+}
+
+SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
+                                    const SourceDir& source_dir,
+                                    BuildDirType type) {
+  return GetSubBuildDirAsOutputFile(context, source_dir, type)
+      .AsSourceDir(context.build_settings);
+}
+
+OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
+                                      const SourceDir& source_dir,
+                                      BuildDirType type) {
+  DCHECK(type != BuildDirType::TOOLCHAIN_ROOT);
+  OutputFile result = GetBuildDirAsOutputFile(context, type);
+
+  if (source_dir.is_source_absolute()) {
+    // The source dir is source-absolute, so we trim off the two leading
+    // slashes to append to the toolchain object directory.
+    result.value().append(&source_dir.value()[2],
+                          source_dir.value().size() - 2);
+  } else {
+    // System-absolute.
+    AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result);
+  }
+  return result;
+}
+
+SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
+                                          BuildDirType type) {
+  return GetSubBuildDirAsSourceDir(BuildDirContext(target),
+                                   target->label().dir(), type);
+}
+
+OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
+                                            BuildDirType type) {
+  return GetSubBuildDirAsOutputFile(BuildDirContext(target),
+                                    target->label().dir(), type);
+}
+
+SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
+                                             BuildDirType type) {
+  if (type == BuildDirType::TOOLCHAIN_ROOT)
+    return GetBuildDirAsSourceDir(BuildDirContext(scope), type);
+  return GetSubBuildDirAsSourceDir(BuildDirContext(scope),
+                                   scope->GetSourceDir(), type);
+}
diff --git a/src/gn/filesystem_utils.h b/src/gn/filesystem_utils.h
new file mode 100644 (file)
index 0000000..830478a
--- /dev/null
@@ -0,0 +1,305 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FILESYSTEM_UTILS_H_
+#define TOOLS_GN_FILESYSTEM_UTILS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <string_view>
+
+#include "base/files/file_path.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+
+class Err;
+
+std::string FilePathToUTF8(const base::FilePath::StringType& str);
+inline std::string FilePathToUTF8(const base::FilePath& path) {
+  return FilePathToUTF8(path.value());
+}
+base::FilePath UTF8ToFilePath(const std::string_view& sp);
+
+// Extensions -----------------------------------------------------------------
+
+// Returns the index of the extension (character after the last dot not after a
+// slash). Returns std::string::npos if not found. Returns path.size() if the
+// file ends with a dot.
+size_t FindExtensionOffset(const std::string& path);
+
+// Returns a string piece pointing into the input string identifying the
+// extension. Note that the input pointer must outlive the output.
+std::string_view FindExtension(const std::string* path);
+
+// Filename parts -------------------------------------------------------------
+
+// Returns the offset of the character following the last slash, or
+// 0 if no slash was found. Returns path.size() if the path ends with a slash.
+// Note that the input pointer must outlive the output.
+size_t FindFilenameOffset(const std::string& path);
+
+// Returns a string piece pointing into the input string identifying the
+// file name (following the last slash, including the extension). Note that the
+// input pointer must outlive the output.
+std::string_view FindFilename(const std::string* path);
+
+// Like FindFilename but does not include the extension.
+std::string_view FindFilenameNoExtension(const std::string* path);
+
+// Removes everything after the last slash. The last slash, if any, will be
+// preserved.
+void RemoveFilename(std::string* path);
+
+// Returns if the given character is a slash. This allows both slashes and
+// backslashes for consistency between Posix and Windows (as opposed to
+// FilePath::IsSeparator which is based on the current platform).
+inline bool IsSlash(const char ch) {
+  return ch == '/' || ch == '\\';
+}
+
+// Returns true if the given path ends with a slash.
+bool EndsWithSlash(const std::string_view s);
+
+// Path parts -----------------------------------------------------------------
+
+// Returns a string piece pointing into the input string identifying the
+// directory name of the given path, including the last slash. Note that the
+// input pointer must outlive the output.
+std::string_view FindDir(const std::string* path);
+
+// Returns the substring identifying the last component of the dir, or the
+// empty substring if none. For example "//foo/bar/" -> "bar".
+std::string_view FindLastDirComponent(const SourceDir& dir);
+
+// Returns true if the given string is in the given output dir. This is pretty
+// stupid and doesn't handle "." and "..", etc., it is designed for a sanity
+// check to keep people from writing output files to the source directory
+// accidentally.
+bool IsStringInOutputDir(const SourceDir& output_dir, const std::string& str);
+
+// Verifies that the given string references a file inside of the given
+// directory. This just uses IsStringInOutputDir above.
+//
+// The origin will be blamed in the error.
+//
+// If the file isn't in the dir, returns false and sets the error. Otherwise
+// returns true and leaves the error untouched.
+bool EnsureStringIsInOutputDir(const SourceDir& output_dir,
+                               const std::string& str,
+                               const ParseNode* origin,
+                               Err* err);
+
+// ----------------------------------------------------------------------------
+
+// Returns true if the input string is absolute. Double-slashes at the
+// beginning are treated as source-relative paths. On Windows, this handles
+// paths of both the native format: "C:/foo" and ours "/C:/foo"
+bool IsPathAbsolute(const std::string_view& path);
+
+// Returns true if the input string is source-absolute. Source-absolute
+// paths begin with two forward slashes and resolve as if they are
+// relative to the source root.
+bool IsPathSourceAbsolute(const std::string_view& path);
+
+// Given an absolute path, checks to see if is it is inside the source root.
+// If it is, fills a source-absolute path into the given output and returns
+// true. If it isn't, clears the dest and returns false.
+//
+// The source_root should be a base::FilePath converted to UTF-8. On Windows,
+// it should begin with a "C:/" rather than being our SourceFile's style
+// ("/C:/"). The source root can end with a slash or not.
+//
+// Note that this does not attempt to normalize slashes in the output.
+bool MakeAbsolutePathRelativeIfPossible(const std::string_view& source_root,
+                                        const std::string_view& path,
+                                        std::string* dest);
+
+// Given two absolute paths |base| and |target|, returns a relative path to
+// |target| as if the current directory was |base|.  The relative path returned
+// is minimal.  For example, if "../../a/b/" and "../b" are both valid, then the
+// latter will be returned.  On Windows, it's impossible to have a relative path
+// from C:\foo to D:\bar, so the absolute path |target| is returned instead for
+// this case.
+base::FilePath MakeAbsoluteFilePathRelativeIfPossible(
+    const base::FilePath& base,
+    const base::FilePath& target);
+
+// Collapses "." and sequential "/"s and evaluates "..". |path| may be
+// system-absolute, source-absolute, or relative. If |path| is source-absolute
+// and |source_root| is non-empty, |path| may be system absolute after this
+// function returns, if |path| references the filesystem outside of
+// |source_root| (ex. path = "//.."). In this case on Windows, |path| will have
+// a leading slash. Otherwise, |path| will retain its relativity. |source_root|
+// must not end with a slash.
+void NormalizePath(std::string* path,
+                   const std::string_view& source_root = std::string_view());
+
+// Converts slashes to backslashes for Windows. Keeps the string unchanged
+// for other systems.
+void ConvertPathToSystem(std::string* path);
+
+// Takes a path, |input|, and makes it relative to the given directory
+// |dest_dir|. Both inputs may be source-relative (e.g. begins with
+// with "//") or may be absolute.
+//
+// If supplied, the |source_root| parameter is the absolute path to
+// the source root and not end in a slash. Unless you know that the
+// inputs are always source relative, this should be supplied.
+std::string RebasePath(
+    const std::string& input,
+    const SourceDir& dest_dir,
+    const std::string_view& source_root = std::string_view());
+
+// Resolves a file or dir name (parameter input) relative to
+// value directory. Will return an empty SourceDir/File on error
+// and set the give *err pointer (required). Empty input is always an error.
+// Returned value can be used to set value in either SourceFile or SourceDir
+// (based on as_file parameter).
+//
+// Parameter as_file defines whether result path will look like a file path
+// or it should be treated as a directory (contains "/" and the end
+// of the string).
+//
+// If source_root is supplied, these functions will additionally handle the
+// case where the input is a system-absolute but still inside the source
+// tree. This is the case for some external tools.
+template <typename StringType>
+std::string ResolveRelative(const StringType& input,
+                            const std::string& value,
+                            bool as_file,
+                            const std::string_view& source_root);
+
+// Resolves source file or directory relative to some given source root. Returns
+// an empty file path on error.
+base::FilePath ResolvePath(const std::string& value,
+                           bool as_file,
+                           const base::FilePath& source_root);
+
+// Returns the given directory with no terminating slash at the end, such that
+// appending a slash and more stuff will produce a valid path.
+//
+// If the directory refers to either the source or system root, we'll append
+// a "." so this remains valid.
+std::string DirectoryWithNoLastSlash(const SourceDir& dir);
+
+// Returns the "best" SourceDir representing the given path. If it's inside the
+// given source_root, a source-relative directory will be returned (e.g.
+// "//foo/bar.cc". If it's outside of the source root or the source root is
+// empty, a system-absolute directory will be returned.
+SourceDir SourceDirForPath(const base::FilePath& source_root,
+                           const base::FilePath& path);
+
+// Like SourceDirForPath but returns the SourceDir representing the current
+// directory.
+SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root);
+
+// Given the label of a toolchain and whether that toolchain is the default
+// toolchain, returns the name of the subdirectory for that toolchain's
+// output. This will be the empty string to indicate that the toolchain outputs
+// go in the root build directory. Otherwise, the result will end in a slash.
+std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default);
+
+// Returns true if the contents of the file and stream given are equal, false
+// otherwise.
+bool ContentsEqual(const base::FilePath& file_path, const std::string& data);
+
+// Writes given stream contents to the given file if it differs from existing
+// file contents. Returns true if new contents was successfully written or
+// existing file contents doesn't need updating, false on write error. |err| is
+// set on write error if not nullptr.
+bool WriteFileIfChanged(const base::FilePath& file_path,
+                        const std::string& data,
+                        Err* err);
+
+// Writes given stream contents to the given file. Returns true if data was
+// successfully written, false otherwise. |err| is set on error if not nullptr.
+bool WriteFile(const base::FilePath& file_path,
+               const std::string& data,
+               Err* err);
+
+// -----------------------------------------------------------------------------
+
+enum class BuildDirType {
+  // Returns the root toolchain dir rather than the generated or output
+  // subdirectories. This is valid only for the toolchain directory getters.
+  // Asking for this for a target or source dir makes no sense.
+  TOOLCHAIN_ROOT,
+
+  // Generated file directory.
+  GEN,
+
+  // Output file directory.
+  OBJ,
+};
+
+// In different contexts, different information is known about the toolchain in
+// question. If you have a Target or settings object, everything can be
+// extracted from there. But when querying label information on something in
+// another toolchain, for example, the only thing known (it may not even exist)
+// is the toolchain label string and whether it matches the default toolchain.
+//
+// This object extracts the relevant information from a variety of input
+// types for the convenience of the caller.
+class BuildDirContext {
+ public:
+  // Extracts toolchain information associated with the given target.
+  explicit BuildDirContext(const Target* target);
+
+  // Extracts toolchain information associated with the given settings object.
+  explicit BuildDirContext(const Settings* settings);
+
+  // Extrats toolchain information from the current toolchain of the scope.
+  explicit BuildDirContext(const Scope* execution_scope);
+
+  // Extracts the default toolchain information from the given execution
+  // scope. The toolchain you want to query must be passed in. This doesn't
+  // use the settings object from the Scope so one can query other toolchains.
+  // If you want to use the scope's current toolchain, use the version above.
+  BuildDirContext(const Scope* execution_scope, const Label& toolchain_label);
+
+  // Specify all information manually.
+  BuildDirContext(const BuildSettings* build_settings,
+                  const Label& toolchain_label,
+                  bool is_default_toolchain);
+
+  const BuildSettings* build_settings;
+  const Label& toolchain_label;
+  bool is_default_toolchain;
+};
+
+// Returns the root, object, or generated file directory for the toolchain.
+//
+// The toolchain object file root is never exposed in GN (there is no
+// root_obj_dir variable) so BuildDirType::OBJ would normally never be passed
+// to this function except when it's called by one of the variants below that
+// append paths to it.
+SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
+                                 BuildDirType type);
+OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
+                                   BuildDirType type);
+
+// Returns the output or generated file directory corresponding to the given
+// source directory.
+SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
+                                    const SourceDir& source_dir,
+                                    BuildDirType type);
+OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
+                                      const SourceDir& source_dir,
+                                      BuildDirType type);
+
+// Returns the output or generated file directory corresponding to the given
+// target.
+SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
+                                          BuildDirType type);
+OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
+                                            BuildDirType type);
+
+// Returns the scope's current directory.
+SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
+                                             BuildDirType type);
+// Lack of OutputDir version is due only to it not currently being needed,
+// please add one if you need it.
+
+#endif  // TOOLS_GN_FILESYSTEM_UTILS_H_
diff --git a/src/gn/filesystem_utils_unittest.cc b/src/gn/filesystem_utils_unittest.cc
new file mode 100644 (file)
index 0000000..347d0fe
--- /dev/null
@@ -0,0 +1,868 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/filesystem_utils.h"
+
+#include <thread>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/target.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(FilesystemUtils, FileExtensionOffset) {
+  EXPECT_EQ(std::string::npos, FindExtensionOffset(""));
+  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo/bar/baz"));
+  EXPECT_EQ(4u, FindExtensionOffset("foo."));
+  EXPECT_EQ(4u, FindExtensionOffset("f.o.bar"));
+  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/"));
+  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/baz"));
+}
+
+TEST(FilesystemUtils, FindExtension) {
+  std::string input;
+  EXPECT_EQ("", FindExtension(&input));
+  input = "foo/bar/baz";
+  EXPECT_EQ("", FindExtension(&input));
+  input = "foo.";
+  EXPECT_EQ("", FindExtension(&input));
+  input = "f.o.bar";
+  EXPECT_EQ("bar", FindExtension(&input));
+  input = "foo.bar/";
+  EXPECT_EQ("", FindExtension(&input));
+  input = "foo.bar/baz";
+  EXPECT_EQ("", FindExtension(&input));
+}
+
+TEST(FilesystemUtils, FindFilenameOffset) {
+  EXPECT_EQ(0u, FindFilenameOffset(""));
+  EXPECT_EQ(0u, FindFilenameOffset("foo"));
+  EXPECT_EQ(4u, FindFilenameOffset("foo/"));
+  EXPECT_EQ(4u, FindFilenameOffset("foo/bar"));
+}
+
+TEST(FilesystemUtils, RemoveFilename) {
+  std::string s;
+
+  RemoveFilename(&s);
+  EXPECT_STREQ("", s.c_str());
+
+  s = "foo";
+  RemoveFilename(&s);
+  EXPECT_STREQ("", s.c_str());
+
+  s = "/";
+  RemoveFilename(&s);
+  EXPECT_STREQ("/", s.c_str());
+
+  s = "foo/bar";
+  RemoveFilename(&s);
+  EXPECT_STREQ("foo/", s.c_str());
+
+  s = "foo/bar/baz.cc";
+  RemoveFilename(&s);
+  EXPECT_STREQ("foo/bar/", s.c_str());
+}
+
+TEST(FilesystemUtils, FindDir) {
+  std::string input;
+  EXPECT_EQ("", FindDir(&input));
+  input = "/";
+  EXPECT_EQ("/", FindDir(&input));
+  input = "foo/";
+  EXPECT_EQ("foo/", FindDir(&input));
+  input = "foo/bar/baz";
+  EXPECT_EQ("foo/bar/", FindDir(&input));
+}
+
+TEST(FilesystemUtils, FindLastDirComponent) {
+  SourceDir empty;
+  EXPECT_EQ("", FindLastDirComponent(empty));
+
+  SourceDir root("/");
+  EXPECT_EQ("", FindLastDirComponent(root));
+
+  SourceDir srcroot("//");
+  EXPECT_EQ("", FindLastDirComponent(srcroot));
+
+  SourceDir regular1("//foo/");
+  EXPECT_EQ("foo", FindLastDirComponent(regular1));
+
+  SourceDir regular2("//foo/bar/");
+  EXPECT_EQ("bar", FindLastDirComponent(regular2));
+}
+
+TEST(FilesystemUtils, EnsureStringIsInOutputDir) {
+  SourceDir output_dir("//out/Debug/");
+
+  // Some outside.
+  Err err;
+  EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//foo", nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+  EXPECT_FALSE(
+      EnsureStringIsInOutputDir(output_dir, "//out/Debugit", nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+
+  // Some inside.
+  err = Err();
+  EXPECT_TRUE(
+      EnsureStringIsInOutputDir(output_dir, "//out/Debug/", nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(
+      EnsureStringIsInOutputDir(output_dir, "//out/Debug/foo", nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+
+  // Pattern but no template expansions are allowed.
+  EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "{{source_gen_dir}}",
+                                         nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilesystemUtils, IsPathAbsolute) {
+  EXPECT_TRUE(IsPathAbsolute("/foo/bar"));
+  EXPECT_TRUE(IsPathAbsolute("/"));
+  EXPECT_FALSE(IsPathAbsolute(""));
+  EXPECT_FALSE(IsPathAbsolute("//"));
+  EXPECT_FALSE(IsPathAbsolute("//foo/bar"));
+
+#if defined(OS_WIN)
+  EXPECT_TRUE(IsPathAbsolute("C:/foo"));
+  EXPECT_TRUE(IsPathAbsolute("C:/"));
+  EXPECT_TRUE(IsPathAbsolute("C:\\foo"));
+  EXPECT_TRUE(IsPathAbsolute("C:\\"));
+  EXPECT_TRUE(IsPathAbsolute("/C:/foo"));
+  EXPECT_TRUE(IsPathAbsolute("/C:\\foo"));
+#endif
+}
+
+TEST(FilesystemUtils, MakeAbsolutePathRelativeIfPossible) {
+  std::string dest;
+
+#if defined(OS_WIN)
+  EXPECT_TRUE(
+      MakeAbsolutePathRelativeIfPossible("C:\\base", "C:\\base\\foo", &dest));
+  EXPECT_EQ("//foo", dest);
+  EXPECT_TRUE(
+      MakeAbsolutePathRelativeIfPossible("C:\\base", "/C:/base/foo", &dest));
+  EXPECT_EQ("//foo", dest);
+  EXPECT_TRUE(
+      MakeAbsolutePathRelativeIfPossible("c:\\base", "C:\\base\\foo\\", &dest));
+  EXPECT_EQ("//foo\\", dest);
+
+  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("C:\\base", "C:\\ba", &dest));
+  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("C:\\base",
+                                                  "C:\\/notbase/foo", &dest));
+#else
+
+  EXPECT_TRUE(MakeAbsolutePathRelativeIfPossible("/base", "/base/foo/", &dest));
+  EXPECT_EQ("//foo/", dest);
+  EXPECT_TRUE(MakeAbsolutePathRelativeIfPossible("/base", "/base/foo", &dest));
+  EXPECT_EQ("//foo", dest);
+  EXPECT_TRUE(
+      MakeAbsolutePathRelativeIfPossible("/base/", "/base/foo/", &dest));
+  EXPECT_EQ("//foo/", dest);
+
+  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("/base", "/ba", &dest));
+  EXPECT_FALSE(
+      MakeAbsolutePathRelativeIfPossible("/base", "/notbase/foo", &dest));
+#endif
+}
+
+TEST(FilesystemUtils, MakeAbsoluteFilePathRelativeIfPossible) {
+#if defined(OS_WIN)
+  EXPECT_EQ(
+      base::FilePath(u"out\\Debug"),
+      MakeAbsoluteFilePathRelativeIfPossible(
+          base::FilePath(u"C:\\src"), base::FilePath(u"C:\\src\\out\\Debug")));
+  EXPECT_EQ(base::FilePath(u".\\gn"),
+            MakeAbsoluteFilePathRelativeIfPossible(
+                base::FilePath(u"C:\\src\\out\\Debug"),
+                base::FilePath(u"C:\\src\\out\\Debug\\gn")));
+  EXPECT_EQ(
+      base::FilePath(u"..\\.."),
+      MakeAbsoluteFilePathRelativeIfPossible(
+          base::FilePath(u"C:\\src\\out\\Debug"), base::FilePath(u"C:\\src")));
+  EXPECT_EQ(
+      base::FilePath(u"..\\.."),
+      MakeAbsoluteFilePathRelativeIfPossible(
+          base::FilePath(u"C:\\src\\out\\Debug"), base::FilePath(u"C:/src")));
+  EXPECT_EQ(base::FilePath(u"."),
+            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath(u"C:\\src"),
+                                                   base::FilePath(u"C:\\src")));
+  EXPECT_EQ(base::FilePath(u"..\\..\\..\\u\\v\\w"),
+            MakeAbsoluteFilePathRelativeIfPossible(
+                base::FilePath(u"C:\\a\\b\\c\\x\\y\\z"),
+                base::FilePath(u"C:\\a\\b\\c\\u\\v\\w")));
+  EXPECT_EQ(base::FilePath(u"D:\\bar"),
+            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath(u"C:\\foo"),
+                                                   base::FilePath(u"D:\\bar")));
+#else
+  EXPECT_EQ(base::FilePath("out/Debug"),
+            MakeAbsoluteFilePathRelativeIfPossible(
+                base::FilePath("/src"), base::FilePath("/src/out/Debug")));
+  EXPECT_EQ(base::FilePath("./gn"), MakeAbsoluteFilePathRelativeIfPossible(
+                                        base::FilePath("/src/out/Debug"),
+                                        base::FilePath("/src/out/Debug/gn")));
+  EXPECT_EQ(base::FilePath("../.."),
+            MakeAbsoluteFilePathRelativeIfPossible(
+                base::FilePath("/src/out/Debug"), base::FilePath("/src")));
+  EXPECT_EQ(base::FilePath("."),
+            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath("/src"),
+                                                   base::FilePath("/src")));
+  EXPECT_EQ(
+      base::FilePath("../../../u/v/w"),
+      MakeAbsoluteFilePathRelativeIfPossible(base::FilePath("/a/b/c/x/y/z"),
+                                             base::FilePath("/a/b/c/u/v/w")));
+#endif
+}
+
+TEST(FilesystemUtils, NormalizePath) {
+  std::string input;
+
+  NormalizePath(&input);
+  EXPECT_EQ("", input);
+
+  input = "foo/bar.txt";
+  NormalizePath(&input);
+  EXPECT_EQ("foo/bar.txt", input);
+
+  input = ".";
+  NormalizePath(&input);
+  EXPECT_EQ("", input);
+
+  input = "..";
+  NormalizePath(&input);
+  EXPECT_EQ("..", input);
+
+  input = "foo//bar";
+  NormalizePath(&input);
+  EXPECT_EQ("foo/bar", input);
+
+  input = "//foo";
+  NormalizePath(&input);
+  EXPECT_EQ("//foo", input);
+
+  input = "foo/..//bar";
+  NormalizePath(&input);
+  EXPECT_EQ("bar", input);
+
+  input = "foo/../../bar";
+  NormalizePath(&input);
+  EXPECT_EQ("../bar", input);
+
+  input = "/../foo";  // Don't go above the root dir.
+  NormalizePath(&input);
+  EXPECT_EQ("/foo", input);
+
+  input = "//../foo";  // Don't go above the root dir.
+  NormalizePath(&input);
+  EXPECT_EQ("//foo", input);
+
+  input = "../foo";
+  NormalizePath(&input);
+  EXPECT_EQ("../foo", input);
+
+  input = "..";
+  NormalizePath(&input);
+  EXPECT_EQ("..", input);
+
+  input = "./././.";
+  NormalizePath(&input);
+  EXPECT_EQ("", input);
+
+  input = "../../..";
+  NormalizePath(&input);
+  EXPECT_EQ("../../..", input);
+
+  input = "../";
+  NormalizePath(&input);
+  EXPECT_EQ("../", input);
+
+  // Backslash normalization.
+  input = "foo\\..\\..\\bar";
+  NormalizePath(&input);
+  EXPECT_EQ("../bar", input);
+
+  // Trailing slashes should get preserved.
+  input = "//foo/bar/";
+  NormalizePath(&input);
+  EXPECT_EQ("//foo/bar/", input);
+
+#if defined(OS_WIN)
+  // Go above and outside of the source root.
+  input = "//../foo";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/foo", input);
+
+  input = "//../foo";
+  NormalizePath(&input, "C:\\source\\root");
+  EXPECT_EQ("/C:/source/foo", input);
+
+  input = "//../";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/", input);
+
+  input = "//../foo.txt";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/foo.txt", input);
+
+  input = "//../foo/bar/";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/foo/bar/", input);
+
+  // Go above and back into the source root. This should return a system-
+  // absolute path. We could arguably return this as a source-absolute path,
+  // but that would require additional handling to account for a rare edge
+  // case.
+  input = "//../root/foo";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/root/foo", input);
+
+  input = "//../root/foo/bar/";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/C:/source/root/foo/bar/", input);
+
+  // Stay inside the source root
+  input = "//foo/bar";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("//foo/bar", input);
+
+  input = "//foo/bar/";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("//foo/bar/", input);
+
+  // The path should not go above the system root. Note that on Windows, this
+  // will consume the drive (C:).
+  input = "//../../../../../foo/bar";
+  NormalizePath(&input, "/C:/source/root");
+  EXPECT_EQ("/foo/bar", input);
+
+  // Test when the source root is the letter drive.
+  input = "//../foo";
+  NormalizePath(&input, "/C:");
+  EXPECT_EQ("/foo", input);
+
+  input = "//../foo";
+  NormalizePath(&input, "C:");
+  EXPECT_EQ("/foo", input);
+
+  input = "//../foo";
+  NormalizePath(&input, "/");
+  EXPECT_EQ("/foo", input);
+
+  input = "//../";
+  NormalizePath(&input, "\\C:");
+  EXPECT_EQ("/", input);
+
+  input = "//../foo.txt";
+  NormalizePath(&input, "/C:");
+  EXPECT_EQ("/foo.txt", input);
+#else
+  // Go above and outside of the source root.
+  input = "//../foo";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/foo", input);
+
+  input = "//../";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/", input);
+
+  input = "//../foo.txt";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/foo.txt", input);
+
+  input = "//../foo/bar/";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/foo/bar/", input);
+
+  // Go above and back into the source root. This should return a system-
+  // absolute path. We could arguably return this as a source-absolute path,
+  // but that would require additional handling to account for a rare edge
+  // case.
+  input = "//../root/foo";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/root/foo", input);
+
+  input = "//../root/foo/bar/";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/source/root/foo/bar/", input);
+
+  // Stay inside the source root
+  input = "//foo/bar";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("//foo/bar", input);
+
+  input = "//foo/bar/";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("//foo/bar/", input);
+
+  // The path should not go above the system root.
+  input = "//../../../../../foo/bar";
+  NormalizePath(&input, "/source/root");
+  EXPECT_EQ("/foo/bar", input);
+
+  // Test when the source root is the system root.
+  input = "//../foo/bar/";
+  NormalizePath(&input, "/");
+  EXPECT_EQ("/foo/bar/", input);
+
+  input = "//../";
+  NormalizePath(&input, "/");
+  EXPECT_EQ("/", input);
+
+  input = "//../foo.txt";
+  NormalizePath(&input, "/");
+  EXPECT_EQ("/foo.txt", input);
+
+#endif
+}
+
+TEST(FilesystemUtils, RebasePath) {
+  std::string_view source_root("/source/root");
+
+  // Degenerate case.
+  EXPECT_EQ(".", RebasePath("//", SourceDir("//"), source_root));
+  EXPECT_EQ(".",
+            RebasePath("//foo/bar/", SourceDir("//foo/bar/"), source_root));
+
+  // Going up the tree.
+  EXPECT_EQ("../foo", RebasePath("//foo", SourceDir("//bar/"), source_root));
+  EXPECT_EQ("../foo/", RebasePath("//foo/", SourceDir("//bar/"), source_root));
+  EXPECT_EQ("../../foo",
+            RebasePath("//foo", SourceDir("//bar/moo"), source_root));
+  EXPECT_EQ("../../foo/",
+            RebasePath("//foo/", SourceDir("//bar/moo"), source_root));
+
+  // Going down the tree.
+  EXPECT_EQ("foo/bar", RebasePath("//foo/bar", SourceDir("//"), source_root));
+  EXPECT_EQ("foo/bar/", RebasePath("//foo/bar/", SourceDir("//"), source_root));
+
+  // Going up and down the tree.
+  EXPECT_EQ("../../foo/bar",
+            RebasePath("//foo/bar", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ("../../foo/bar/",
+            RebasePath("//foo/bar/", SourceDir("//a/b/"), source_root));
+
+  // Sharing prefix.
+  EXPECT_EQ("foo", RebasePath("//a/foo", SourceDir("//a/"), source_root));
+  EXPECT_EQ("foo", RebasePath("//a/foo", SourceDir("//a"), source_root));
+  EXPECT_EQ("foo/", RebasePath("//a/foo/", SourceDir("//a/"), source_root));
+  EXPECT_EQ("foo", RebasePath("//a/b/foo", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ("foo/", RebasePath("//a/b/foo/", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ("foo/bar",
+            RebasePath("//a/b/foo/bar", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ("foo/bar/",
+            RebasePath("//a/b/foo/bar/", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ(".",
+            RebasePath("//foo/bar", SourceDir("//foo/bar/"), source_root));
+  EXPECT_EQ("..",
+            RebasePath("//foo", SourceDir("//foo/bar/"), source_root));
+  EXPECT_EQ("../",
+            RebasePath("//foo/", SourceDir("//foo/bar/"), source_root));
+
+  // Check when only |input| is system-absolute
+  EXPECT_EQ("foo", RebasePath("/source/root/foo", SourceDir("//"),
+                              std::string_view("/source/root")));
+  EXPECT_EQ("foo/", RebasePath("/source/root/foo/", SourceDir("//"),
+                               std::string_view("/source/root")));
+  EXPECT_EQ("../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("//"),
+                       std::string_view("/source/root")));
+  EXPECT_EQ("../../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("//"),
+                       std::string_view("/source/root/foo")));
+  EXPECT_EQ("../../../builddir/Out/Debug/",
+            RebasePath("/builddir/Out/Debug/", SourceDir("//"),
+                       std::string_view("/source/root/foo")));
+  EXPECT_EQ("../../path/to/foo", RebasePath("/path/to/foo", SourceDir("//"),
+                                            std::string_view("/source/root")));
+  EXPECT_EQ("../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("//a"),
+                       std::string_view("/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("/source/root")));
+
+  // Check when only |dest_dir| is system-absolute.
+  EXPECT_EQ(".", RebasePath("//", SourceDir("/source/root"),
+                            std::string_view("/source/root")));
+  EXPECT_EQ("foo", RebasePath("//foo", SourceDir("/source/root"),
+                              std::string_view("/source/root")));
+  EXPECT_EQ("../foo", RebasePath("//foo", SourceDir("/source/root/bar"),
+                                 std::string_view("/source/root")));
+  EXPECT_EQ("../../../source/root/foo",
+            RebasePath("//foo", SourceDir("/other/source/root"),
+                       std::string_view("/source/root")));
+  EXPECT_EQ("../../../../source/root/foo",
+            RebasePath("//foo", SourceDir("/other/source/root/bar"),
+                       std::string_view("/source/root")));
+
+  // Check when |input| and |dest_dir| are both system-absolute. Also,
+  // in this case |source_root| is never used so set it to a dummy
+  // value.
+  EXPECT_EQ("foo", RebasePath("/source/root/foo", SourceDir("/source/root"),
+                              std::string_view("/x/y/z")));
+  EXPECT_EQ("foo/", RebasePath("/source/root/foo/", SourceDir("/source/root"),
+                               std::string_view("/x/y/z")));
+  EXPECT_EQ("../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("/source/root"),
+                       std::string_view("/x/y/z")));
+  EXPECT_EQ("../../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("/source/root/foo"),
+                       std::string_view("/source/root/foo")));
+  EXPECT_EQ("../../../builddir/Out/Debug/",
+            RebasePath("/builddir/Out/Debug/", SourceDir("/source/root/foo"),
+                       std::string_view("/source/root/foo")));
+  EXPECT_EQ("../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root"),
+                       std::string_view("/x/y/z")));
+  EXPECT_EQ("../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root/a"),
+                       std::string_view("/x/y/z")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root/a/b"),
+                       std::string_view("/x/y/z")));
+
+#if defined(OS_WIN)
+  // Test corrections while rebasing Windows-style absolute paths.
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("C:/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("/C:/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/C:/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("C:/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/C:/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("/c:/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("c:/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
+                       std::string_view("C:/source/root")));
+#endif
+}
+
+TEST(FilesystemUtils, DirectoryWithNoLastSlash) {
+  EXPECT_EQ("", DirectoryWithNoLastSlash(SourceDir()));
+  EXPECT_EQ("/.", DirectoryWithNoLastSlash(SourceDir("/")));
+  EXPECT_EQ("//.", DirectoryWithNoLastSlash(SourceDir("//")));
+  EXPECT_EQ("//foo", DirectoryWithNoLastSlash(SourceDir("//foo/")));
+  EXPECT_EQ("/bar", DirectoryWithNoLastSlash(SourceDir("/bar/")));
+}
+
+TEST(FilesystemUtils, SourceDirForPath) {
+#if defined(OS_WIN)
+  base::FilePath root(u"C:\\source\\foo\\");
+  EXPECT_EQ("/C:/foo/bar/",
+            SourceDirForPath(root, base::FilePath(u"C:\\foo\\bar")).value());
+  EXPECT_EQ("/", SourceDirForPath(root, base::FilePath(u"/")).value());
+  EXPECT_EQ("//",
+            SourceDirForPath(root, base::FilePath(u"C:\\source\\foo")).value());
+  EXPECT_EQ("//bar/",
+            SourceDirForPath(root, base::FilePath(u"C:\\source\\foo\\bar\\"))
+                .value());
+  EXPECT_EQ("//bar/baz/",
+            SourceDirForPath(root, base::FilePath(u"C:\\source\\foo\\bar\\baz"))
+                .value());
+
+  // Should be case-and-slash-insensitive.
+  EXPECT_EQ(
+      "//baR/",
+      SourceDirForPath(root, base::FilePath(u"c:/SOURCE\\Foo/baR/")).value());
+
+  // Some "weird" Windows paths.
+  EXPECT_EQ("/foo/bar/",
+            SourceDirForPath(root, base::FilePath(u"/foo/bar/")).value());
+  EXPECT_EQ("/C:/foo/bar/",
+            SourceDirForPath(root, base::FilePath(u"C:foo/bar/")).value());
+
+  // Also allow absolute GN-style Windows paths.
+  EXPECT_EQ("/C:/foo/bar/",
+            SourceDirForPath(root, base::FilePath(u"/C:/foo/bar")).value());
+  EXPECT_EQ(
+      "//bar/",
+      SourceDirForPath(root, base::FilePath(u"/C:/source/foo/bar")).value());
+
+  // Empty source dir.
+  base::FilePath empty;
+  EXPECT_EQ(
+      "/C:/source/foo/",
+      SourceDirForPath(empty, base::FilePath(u"C:\\source\\foo")).value());
+#else
+  base::FilePath root("/source/foo/");
+  EXPECT_EQ("/foo/bar/",
+            SourceDirForPath(root, base::FilePath("/foo/bar/")).value());
+  EXPECT_EQ("/", SourceDirForPath(root, base::FilePath("/")).value());
+  EXPECT_EQ("//",
+            SourceDirForPath(root, base::FilePath("/source/foo")).value());
+  EXPECT_EQ("//bar/",
+            SourceDirForPath(root, base::FilePath("/source/foo/bar/")).value());
+  EXPECT_EQ(
+      "//bar/baz/",
+      SourceDirForPath(root, base::FilePath("/source/foo/bar/baz/")).value());
+
+  // Should be case-sensitive.
+  EXPECT_EQ("/SOURCE/foo/bar/",
+            SourceDirForPath(root, base::FilePath("/SOURCE/foo/bar/")).value());
+
+  // Empty source dir.
+  base::FilePath empty;
+  EXPECT_EQ("/source/foo/",
+            SourceDirForPath(empty, base::FilePath("/source/foo")).value());
+#endif
+}
+
+TEST(FilesystemUtils, ContentsEqual) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  std::string data = "foo";
+
+  base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
+  base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size()));
+
+  EXPECT_TRUE(ContentsEqual(file_path, data));
+
+  // Different length and contents.
+  data += "bar";
+  EXPECT_FALSE(ContentsEqual(file_path, data));
+
+  // The same length, different contents.
+  EXPECT_FALSE(ContentsEqual(file_path, "bar"));
+}
+
+TEST(FilesystemUtils, WriteFileIfChanged) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  std::string data = "foo";
+
+  // Write if file doesn't exist. Create also directory.
+  base::FilePath file_path =
+      temp_dir.GetPath().AppendASCII("bar").AppendASCII("foo.txt");
+  EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+  Ticks last_modified = file_info.last_modified;
+
+  {
+    using namespace std::chrono_literals;
+#if defined(OS_MACOSX)
+    // Modification times are in seconds in HFS on Mac.
+    std::this_thread::sleep_for(1s);
+#else
+    std::this_thread::sleep_for(1ms);
+#endif
+  }
+
+  // Don't write if contents is the same.
+  EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+  EXPECT_EQ(last_modified, file_info.last_modified);
+
+  // Write if contents changed.
+  EXPECT_TRUE(WriteFileIfChanged(file_path, "bar", nullptr));
+  std::string file_data;
+  ASSERT_TRUE(base::ReadFileToString(file_path, &file_data));
+  EXPECT_EQ("bar", file_data);
+}
+
+TEST(FilesystemUtils, GetToolchainDirs) {
+  BuildSettings build_settings;
+  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+
+  // The default toolchain.
+  Settings default_settings(&build_settings, "");
+  Label default_toolchain_label(SourceDir("//toolchain/"), "default");
+  default_settings.set_toolchain_label(default_toolchain_label);
+  default_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext default_context(&default_settings);
+
+  // Default toolchain out dir as source dirs.
+  EXPECT_EQ("//out/Debug/", GetBuildDirAsSourceDir(default_context,
+                                                   BuildDirType::TOOLCHAIN_ROOT)
+                                .value());
+  EXPECT_EQ("//out/Debug/obj/",
+            GetBuildDirAsSourceDir(default_context, BuildDirType::OBJ).value());
+  EXPECT_EQ("//out/Debug/gen/",
+            GetBuildDirAsSourceDir(default_context, BuildDirType::GEN).value());
+
+  // Default toolchain our dir as output files.
+  EXPECT_EQ(
+      "", GetBuildDirAsOutputFile(default_context, BuildDirType::TOOLCHAIN_ROOT)
+              .value());
+  EXPECT_EQ(
+      "obj/",
+      GetBuildDirAsOutputFile(default_context, BuildDirType::OBJ).value());
+  EXPECT_EQ(
+      "gen/",
+      GetBuildDirAsOutputFile(default_context, BuildDirType::GEN).value());
+
+  // Check a secondary toolchain.
+  Settings other_settings(&build_settings, "two/");
+  Label other_toolchain_label(SourceDir("//toolchain/"), "two");
+  other_settings.set_toolchain_label(other_toolchain_label);
+  other_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext other_context(&other_settings);
+
+  // Secondary toolchain out dir as source dirs.
+  EXPECT_EQ("//out/Debug/two/",
+            GetBuildDirAsSourceDir(other_context, BuildDirType::TOOLCHAIN_ROOT)
+                .value());
+  EXPECT_EQ("//out/Debug/two/obj/",
+            GetBuildDirAsSourceDir(other_context, BuildDirType::OBJ).value());
+  EXPECT_EQ("//out/Debug/two/gen/",
+            GetBuildDirAsSourceDir(other_context, BuildDirType::GEN).value());
+
+  // Secondary toolchain out dir as output files.
+  EXPECT_EQ("two/",
+            GetBuildDirAsOutputFile(other_context, BuildDirType::TOOLCHAIN_ROOT)
+                .value());
+  EXPECT_EQ("two/obj/",
+            GetBuildDirAsOutputFile(other_context, BuildDirType::OBJ).value());
+  EXPECT_EQ("two/gen/",
+            GetBuildDirAsOutputFile(other_context, BuildDirType::GEN).value());
+}
+
+TEST(FilesystemUtils, GetSubBuildDir) {
+  BuildSettings build_settings;
+  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+
+  // Test the default toolchain.
+  Label default_toolchain_label(SourceDir("//toolchain/"), "default");
+  Settings default_settings(&build_settings, "");
+  default_settings.set_toolchain_label(default_toolchain_label);
+  default_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext default_context(&default_settings);
+
+  // Target in the root.
+  EXPECT_EQ("//out/Debug/obj/",
+            GetSubBuildDirAsSourceDir(default_context, SourceDir("//"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("gen/", GetSubBuildDirAsOutputFile(default_context, SourceDir("//"),
+                                               BuildDirType::GEN)
+                        .value());
+
+  // Target in another directory.
+  EXPECT_EQ("//out/Debug/obj/foo/bar/",
+            GetSubBuildDirAsSourceDir(default_context, SourceDir("//foo/bar/"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("gen/foo/bar/",
+            GetSubBuildDirAsOutputFile(default_context, SourceDir("//foo/bar/"),
+                                       BuildDirType::GEN)
+                .value());
+
+  // Secondary toolchain.
+  Settings other_settings(&build_settings, "two/");
+  other_settings.set_toolchain_label(Label(SourceDir("//toolchain/"), "two"));
+  other_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext other_context(&other_settings);
+
+  // Target in the root.
+  EXPECT_EQ("//out/Debug/two/obj/",
+            GetSubBuildDirAsSourceDir(other_context, SourceDir("//"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("two/gen/", GetSubBuildDirAsOutputFile(
+                            other_context, SourceDir("//"), BuildDirType::GEN)
+                            .value());
+
+  // Target in another directory.
+  EXPECT_EQ("//out/Debug/two/obj/foo/bar/",
+            GetSubBuildDirAsSourceDir(other_context, SourceDir("//foo/bar/"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("two/gen/foo/bar/",
+            GetSubBuildDirAsOutputFile(other_context, SourceDir("//foo/bar/"),
+                                       BuildDirType::GEN)
+                .value());
+
+  // Absolute source path
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/abs/",
+            GetSubBuildDirAsSourceDir(default_context, SourceDir("/abs"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("gen/ABS_PATH/abs/",
+            GetSubBuildDirAsOutputFile(default_context, SourceDir("/abs"),
+                                       BuildDirType::GEN)
+                .value());
+#if defined(OS_WIN)
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/abs/",
+            GetSubBuildDirAsSourceDir(default_context, SourceDir("/C:/abs"),
+                                      BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("gen/ABS_PATH/C/abs/",
+            GetSubBuildDirAsOutputFile(default_context, SourceDir("/C:/abs"),
+                                       BuildDirType::GEN)
+                .value());
+#endif
+}
+
+TEST(FilesystemUtils, GetBuildDirForTarget) {
+  BuildSettings build_settings;
+  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+  Settings settings(&build_settings, "");
+
+  Target a(&settings, Label(SourceDir("//foo/bar/"), "baz"));
+  EXPECT_EQ("//out/Debug/obj/foo/bar/",
+            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::OBJ).value());
+  EXPECT_EQ("obj/foo/bar/",
+            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::OBJ).value());
+  EXPECT_EQ("//out/Debug/gen/foo/bar/",
+            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::GEN).value());
+  EXPECT_EQ("gen/foo/bar/",
+            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::GEN).value());
+}
+
+// Tests handling of output dirs when build dir is the same as the root.
+TEST(FilesystemUtils, GetDirForEmptyBuildDir) {
+  BuildSettings build_settings;
+  build_settings.SetBuildDir(SourceDir("//"));
+  Settings settings(&build_settings, "");
+
+  BuildDirContext context(&settings);
+
+  EXPECT_EQ(
+      "//",
+      GetBuildDirAsSourceDir(context, BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("//gen/",
+            GetBuildDirAsSourceDir(context, BuildDirType::GEN).value());
+  EXPECT_EQ("//obj/",
+            GetBuildDirAsSourceDir(context, BuildDirType::OBJ).value());
+
+  EXPECT_EQ(
+      "",
+      GetBuildDirAsOutputFile(context, BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("gen/",
+            GetBuildDirAsOutputFile(context, BuildDirType::GEN).value());
+  EXPECT_EQ("obj/",
+            GetBuildDirAsOutputFile(context, BuildDirType::OBJ).value());
+}
+
+TEST(FilesystemUtils, ResolveRelativeTest) {
+  std::string result;
+#ifndef OS_WIN
+  EXPECT_TRUE(
+      MakeAbsolutePathRelativeIfPossible("/some/dir", "/some/dir/a", &result));
+  EXPECT_EQ(result, "//a");
+
+  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible(
+      "/some/dir", "/some/dir-sufix/a", &result));
+#else
+  EXPECT_TRUE(MakeAbsolutePathRelativeIfPossible("C:/some/dir",
+                                                 "/C:/some/dir/a", &result));
+  EXPECT_EQ(result, "//a");
+  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible(
+      "C:/some/dir", "C:/some/dir-sufix/a", &result));
+#endif
+}
diff --git a/src/gn/format_test_data/001.gn b/src/gn/format_test_data/001.gn
new file mode 100644 (file)
index 0000000..c35c279
--- /dev/null
@@ -0,0 +1,2 @@
+# Test.
+executable("test"){}
diff --git a/src/gn/format_test_data/001.golden b/src/gn/format_test_data/001.golden
new file mode 100644 (file)
index 0000000..31c9069
--- /dev/null
@@ -0,0 +1,3 @@
+# Test.
+executable("test") {
+}
diff --git a/src/gn/format_test_data/002.gn b/src/gn/format_test_data/002.gn
new file mode 100644 (file)
index 0000000..34043b5
--- /dev/null
@@ -0,0 +1,6 @@
+executable("test") {
+  sources = [
+    "stuff.cc",
+    "things.cc"
+  ]
+}
diff --git a/src/gn/format_test_data/002.golden b/src/gn/format_test_data/002.golden
new file mode 100644 (file)
index 0000000..cd8f388
--- /dev/null
@@ -0,0 +1,6 @@
+executable("test") {
+  sources = [
+    "stuff.cc",
+    "things.cc",
+  ]
+}
diff --git a/src/gn/format_test_data/003.gn b/src/gn/format_test_data/003.gn
new file mode 100644 (file)
index 0000000..429165a
--- /dev/null
@@ -0,0 +1,10 @@
+executable("test") {
+  sources = [
+    "stuff.cc",
+    "things.cc"
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
diff --git a/src/gn/format_test_data/003.golden b/src/gn/format_test_data/003.golden
new file mode 100644 (file)
index 0000000..a5bd5d3
--- /dev/null
@@ -0,0 +1,8 @@
+executable("test") {
+  sources = [
+    "stuff.cc",
+    "things.cc",
+  ]
+
+  deps = [ "//base" ]
+}
diff --git a/src/gn/format_test_data/004.gn b/src/gn/format_test_data/004.gn
new file mode 100644 (file)
index 0000000..f36d039
--- /dev/null
@@ -0,0 +1,10 @@
+# This is a block comment that goes at the top of the file and is attached to
+# the top level target.
+executable("test") {
+  sources = [
+    "stuff.cc",# Comment attached to list item.
+    "things.cc"
+  ]
+  # Comment attached to statement.
+  deps = [ "//base", ]
+}
diff --git a/src/gn/format_test_data/004.golden b/src/gn/format_test_data/004.golden
new file mode 100644 (file)
index 0000000..1d000c6
--- /dev/null
@@ -0,0 +1,11 @@
+# This is a block comment that goes at the top of the file and is attached to
+# the top level target.
+executable("test") {
+  sources = [
+    "stuff.cc",  # Comment attached to list item.
+    "things.cc",
+  ]
+
+  # Comment attached to statement.
+  deps = [ "//base" ]
+}
diff --git a/src/gn/format_test_data/005.gn b/src/gn/format_test_data/005.gn
new file mode 100644 (file)
index 0000000..07b4ef3
--- /dev/null
@@ -0,0 +1,5 @@
+# This is a separated block comment that mustn't be attached the to target
+# below, and should be separated by a single blank line.
+
+executable("test") {
+}
diff --git a/src/gn/format_test_data/005.golden b/src/gn/format_test_data/005.golden
new file mode 100644 (file)
index 0000000..07b4ef3
--- /dev/null
@@ -0,0 +1,5 @@
+# This is a separated block comment that mustn't be attached the to target
+# below, and should be separated by a single blank line.
+
+executable("test") {
+}
diff --git a/src/gn/format_test_data/006.gn b/src/gn/format_test_data/006.gn
new file mode 100644 (file)
index 0000000..737fcea
--- /dev/null
@@ -0,0 +1,9 @@
+# This is a separated block comment that mustn't be attached the to target
+# below, and should be separated by a single blank line.
+
+
+
+
+
+executable("test") {
+}
diff --git a/src/gn/format_test_data/006.golden b/src/gn/format_test_data/006.golden
new file mode 100644 (file)
index 0000000..07b4ef3
--- /dev/null
@@ -0,0 +1,5 @@
+# This is a separated block comment that mustn't be attached the to target
+# below, and should be separated by a single blank line.
+
+executable("test") {
+}
diff --git a/src/gn/format_test_data/007.gn b/src/gn/format_test_data/007.gn
new file mode 100644 (file)
index 0000000..3cd002d
--- /dev/null
@@ -0,0 +1,9 @@
+executable("test") {
+  sources = ["a.cc"]
+
+  # This is an unusual comment that's a header for the stuff that comes after
+  # it. We want to make sure that it's not connected to the next element in
+  # the file.
+
+  cflags = ["-Wee"]
+}
diff --git a/src/gn/format_test_data/007.golden b/src/gn/format_test_data/007.golden
new file mode 100644 (file)
index 0000000..2d2b4c1
--- /dev/null
@@ -0,0 +1,9 @@
+executable("test") {
+  sources = [ "a.cc" ]
+
+  # This is an unusual comment that's a header for the stuff that comes after
+  # it. We want to make sure that it's not connected to the next element in
+  # the file.
+
+  cflags = [ "-Wee" ]
+}
diff --git a/src/gn/format_test_data/008.gn b/src/gn/format_test_data/008.gn
new file mode 100644 (file)
index 0000000..16b4a8e
--- /dev/null
@@ -0,0 +1 @@
+if (is_win) { sources = ["win.cc"] }
diff --git a/src/gn/format_test_data/008.golden b/src/gn/format_test_data/008.golden
new file mode 100644 (file)
index 0000000..8e4c5f1
--- /dev/null
@@ -0,0 +1,3 @@
+if (is_win) {
+  sources = [ "win.cc" ]
+}
diff --git a/src/gn/format_test_data/009.gn b/src/gn/format_test_data/009.gn
new file mode 100644 (file)
index 0000000..e47b621
--- /dev/null
@@ -0,0 +1,2 @@
+if (is_win) { sources = ["win.cc"] }
+else { sources = ["linux.cc"] }
diff --git a/src/gn/format_test_data/009.golden b/src/gn/format_test_data/009.golden
new file mode 100644 (file)
index 0000000..20663f8
--- /dev/null
@@ -0,0 +1,5 @@
+if (is_win) {
+  sources = [ "win.cc" ]
+} else {
+  sources = [ "linux.cc" ]
+}
diff --git a/src/gn/format_test_data/010.gn b/src/gn/format_test_data/010.gn
new file mode 100644 (file)
index 0000000..70004a7
--- /dev/null
@@ -0,0 +1,2 @@
+if (is_win) { sources = ["win.cc"] }
+else if (is_linux) { sources = ["linux.cc"] }
diff --git a/src/gn/format_test_data/010.golden b/src/gn/format_test_data/010.golden
new file mode 100644 (file)
index 0000000..9512a2e
--- /dev/null
@@ -0,0 +1,5 @@
+if (is_win) {
+  sources = [ "win.cc" ]
+} else if (is_linux) {
+  sources = [ "linux.cc" ]
+}
diff --git a/src/gn/format_test_data/011.gn b/src/gn/format_test_data/011.gn
new file mode 100644 (file)
index 0000000..c2c5bf7
--- /dev/null
@@ -0,0 +1,4 @@
+if (is_win) { sources = ["win.cc"] }
+else if (is_linux) { sources = ["linux.cc"] }
+else { sources = ["wha.cc"] }
+
diff --git a/src/gn/format_test_data/011.golden b/src/gn/format_test_data/011.golden
new file mode 100644 (file)
index 0000000..43d70c5
--- /dev/null
@@ -0,0 +1,7 @@
+if (is_win) {
+  sources = [ "win.cc" ]
+} else if (is_linux) {
+  sources = [ "linux.cc" ]
+} else {
+  sources = [ "wha.cc" ]
+}
diff --git a/src/gn/format_test_data/012.gn b/src/gn/format_test_data/012.gn
new file mode 100644 (file)
index 0000000..1da385c
--- /dev/null
@@ -0,0 +1,16 @@
+# (A sample top level block comment)
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (is_win) {
+    # This is some special stuff for Windows
+    sources = ["win.cc"] } else if (is_linux) {
+
+    # This is a block comment inside the linux block, but not attached.
+
+    sources = ["linux.cc"]
+} else {
+  # A comment with trailing spaces            
+sources = ["wha.cc"] }
+
diff --git a/src/gn/format_test_data/012.golden b/src/gn/format_test_data/012.golden
new file mode 100644 (file)
index 0000000..7e3fc98
--- /dev/null
@@ -0,0 +1,16 @@
+# (A sample top level block comment)
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (is_win) {
+  # This is some special stuff for Windows
+  sources = [ "win.cc" ]
+} else if (is_linux) {
+  # This is a block comment inside the linux block, but not attached.
+
+  sources = [ "linux.cc" ]
+} else {
+  # A comment with trailing spaces
+  sources = [ "wha.cc" ]
+}
diff --git a/src/gn/format_test_data/013.gn b/src/gn/format_test_data/013.gn
new file mode 100644 (file)
index 0000000..06183a8
--- /dev/null
@@ -0,0 +1,7 @@
+defines = [
+    # Separate comment inside expression.
+
+    # Connected comment.
+    "WEE",
+    "BLORPY",
+]
diff --git a/src/gn/format_test_data/013.golden b/src/gn/format_test_data/013.golden
new file mode 100644 (file)
index 0000000..d84b7d2
--- /dev/null
@@ -0,0 +1,7 @@
+defines = [
+  # Separate comment inside expression.
+
+  # Connected comment.
+  "WEE",
+  "BLORPY",
+]
diff --git a/src/gn/format_test_data/014.gn b/src/gn/format_test_data/014.gn
new file mode 100644 (file)
index 0000000..2d0170d
--- /dev/null
@@ -0,0 +1,6 @@
+defines = [
+
+    # Connected comment.
+    "WEE",
+    "BLORPY",
+]
diff --git a/src/gn/format_test_data/014.golden b/src/gn/format_test_data/014.golden
new file mode 100644 (file)
index 0000000..c0ba5bb
--- /dev/null
@@ -0,0 +1,5 @@
+defines = [
+  # Connected comment.
+  "WEE",
+  "BLORPY",
+]
diff --git a/src/gn/format_test_data/015.gn b/src/gn/format_test_data/015.gn
new file mode 100644 (file)
index 0000000..f065095
--- /dev/null
@@ -0,0 +1,4 @@
+if (is_win) {
+  sources = ["a.cc"]
+  # Some comment at end.
+}
diff --git a/src/gn/format_test_data/015.golden b/src/gn/format_test_data/015.golden
new file mode 100644 (file)
index 0000000..8e12688
--- /dev/null
@@ -0,0 +1,4 @@
+if (is_win) {
+  sources = [ "a.cc" ]
+  # Some comment at end.
+}
diff --git a/src/gn/format_test_data/016.gn b/src/gn/format_test_data/016.gn
new file mode 100644 (file)
index 0000000..00a7992
--- /dev/null
@@ -0,0 +1 @@
+something = !is_win && is_linux || is_mac && !(is_freebsd || is_ios)
diff --git a/src/gn/format_test_data/016.golden b/src/gn/format_test_data/016.golden
new file mode 100644 (file)
index 0000000..3f4f15b
--- /dev/null
@@ -0,0 +1 @@
+something = (!is_win && is_linux) || (is_mac && !(is_freebsd || is_ios))
diff --git a/src/gn/format_test_data/017.gn b/src/gn/format_test_data/017.gn
new file mode 100644 (file)
index 0000000..225ae2f
--- /dev/null
@@ -0,0 +1,15 @@
+executable("win" # Suffix comment on arg.
+           # And a strangely positioned line comment for some reason
+          ) {
+    defines = [ # Defines comment, suffix at end position.
+    ]
+
+    deps = [
+      # Deps comment, should be forced multiline.
+    ]
+    sources = [
+      "a.cc"
+      # End of single sources comment, should be forced multiline.
+    ]
+    # End of block comment.
+}
diff --git a/src/gn/format_test_data/017.golden b/src/gn/format_test_data/017.golden
new file mode 100644 (file)
index 0000000..d9d7ad9
--- /dev/null
@@ -0,0 +1,16 @@
+executable("win"  # Suffix comment on arg.
+
+           # And a strangely positioned line comment for some reason
+           ) {
+  defines = []  # Defines comment, suffix at end position.
+
+  deps = [
+    # Deps comment, should be forced multiline.
+  ]
+  sources = [
+    "a.cc",
+    # End of single sources comment, should be forced multiline.
+  ]
+
+  # End of block comment.
+}
diff --git a/src/gn/format_test_data/018.gn b/src/gn/format_test_data/018.gn
new file mode 100644 (file)
index 0000000..e63ef9d
--- /dev/null
@@ -0,0 +1,3 @@
+# Don't crash when no block on a function call.
+
+import("wee.gni")
diff --git a/src/gn/format_test_data/018.golden b/src/gn/format_test_data/018.golden
new file mode 100644 (file)
index 0000000..e63ef9d
--- /dev/null
@@ -0,0 +1,3 @@
+# Don't crash when no block on a function call.
+
+import("wee.gni")
diff --git a/src/gn/format_test_data/019.gn b/src/gn/format_test_data/019.gn
new file mode 100644 (file)
index 0000000..125480c
--- /dev/null
@@ -0,0 +1,23 @@
+# Make sure blank lines are maintained before comments in lists.
+
+deps = [
+      "//pdf",  # Not compiled on Android in GYP yet, either.
+      "//ppapi:ppapi_c",
+      "//third_party/libusb",
+      "//ui/keyboard",  # Blocked on content.
+
+      # Seems to not be compiled on Android. Otherwise it will need a config.h.
+      "//third_party/libxslt",
+
+      # Not relevant to Android.
+      "//ash",
+      "//gn",
+
+      # Multiple line
+      # comment
+      # here.
+      "//ui/aura",
+      "//ui/display",
+      "//ui/views",
+      "//ui/views/controls/webview",
+]
diff --git a/src/gn/format_test_data/019.golden b/src/gn/format_test_data/019.golden
new file mode 100644 (file)
index 0000000..256c171
--- /dev/null
@@ -0,0 +1,23 @@
+# Make sure blank lines are maintained before comments in lists.
+
+deps = [
+  "//pdf",  # Not compiled on Android in GYP yet, either.
+  "//ppapi:ppapi_c",
+  "//third_party/libusb",
+  "//ui/keyboard",  # Blocked on content.
+
+  # Seems to not be compiled on Android. Otherwise it will need a config.h.
+  "//third_party/libxslt",
+
+  # Not relevant to Android.
+  "//ash",
+  "//gn",
+
+  # Multiple line
+  # comment
+  # here.
+  "//ui/aura",
+  "//ui/display",
+  "//ui/views",
+  "//ui/views/controls/webview",
+]
diff --git a/src/gn/format_test_data/020.gn b/src/gn/format_test_data/020.gn
new file mode 100644 (file)
index 0000000..96de164
--- /dev/null
@@ -0,0 +1,5 @@
+cflags = [
+  "/wd4267",  # size_t -> int.
+  "/wd4324",  # Structure was padded due to __declspec(align()), which is
+              # uninteresting.
+]
diff --git a/src/gn/format_test_data/020.golden b/src/gn/format_test_data/020.golden
new file mode 100644 (file)
index 0000000..96de164
--- /dev/null
@@ -0,0 +1,5 @@
+cflags = [
+  "/wd4267",  # size_t -> int.
+  "/wd4324",  # Structure was padded due to __declspec(align()), which is
+              # uninteresting.
+]
diff --git a/src/gn/format_test_data/021.gn b/src/gn/format_test_data/021.gn
new file mode 100644 (file)
index 0000000..355735d
--- /dev/null
@@ -0,0 +1,33 @@
+f(aaaaaaaaaaaaaaaaaaa)
+
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa)
+
+# Exactly 80 wide.
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaa)
+
+aaaaaaaaaa(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaa.aaaaaaaaaaaaaaa)
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaa(aaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaa(aaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa))
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaaa)
+
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaaa)
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa)
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa)
+
+somefunction(someotherFunction(ddddddddddddddddddddddddddddddddddd, ddddddddddddddddddddddddddddd), test)
diff --git a/src/gn/format_test_data/021.golden b/src/gn/format_test_data/021.golden
new file mode 100644 (file)
index 0000000..f17c5fd
--- /dev/null
@@ -0,0 +1,61 @@
+f(aaaaaaaaaaaaaaaaaaa)
+
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa)
+
+# Exactly 80 wide.
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaa)
+
+aaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaa.aaaaaaaaaaaaaaa)
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaa(aaaaaaaaaaaaa,
+        aaaaaaaaaaaaa,
+        aaaaaaaaaaaaa(aaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa))
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaaaaa +
+      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaa,
+             aaaaaaaaaaaa,
+             aaaaaaaaaaaa)
+
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
+                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+             aaaaaaaaaaaa,
+             aaaaaaaaaaaa)
+
+aaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaa)
+
+aaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaa)
+
+somefunction(someotherFunction(ddddddddddddddddddddddddddddddddddd,
+                               ddddddddddddddddddddddddddddd),
+             test)
diff --git a/src/gn/format_test_data/022.gn b/src/gn/format_test_data/022.gn
new file mode 100644 (file)
index 0000000..a67ed24
--- /dev/null
@@ -0,0 +1,6 @@
+executable(something[0]) {
+  if (weeeeee.stuff) {
+    x = a.b
+    y = a[8]
+  }
+}
diff --git a/src/gn/format_test_data/022.golden b/src/gn/format_test_data/022.golden
new file mode 100644 (file)
index 0000000..a67ed24
--- /dev/null
@@ -0,0 +1,6 @@
+executable(something[0]) {
+  if (weeeeee.stuff) {
+    x = a.b
+    y = a[8]
+  }
+}
diff --git a/src/gn/format_test_data/023.gn b/src/gn/format_test_data/023.gn
new file mode 100644 (file)
index 0000000..8a1b519
--- /dev/null
@@ -0,0 +1,38 @@
+f(aaaaaaaaaaaaaaaaaaa) {}
+
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa) {}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaa) {}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaa) {}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa, aaaaaaaaa) {}
+
+aaaaaaaaaa(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaa.aaaaaaaaaaaaaaa) {}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaa) {}
+
+aaaaaaa(aaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaa(aaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa)) {}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {}
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaaa) {}
+
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaaa) {}
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa) {}
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaa) {}
+
+somefunction(someotherFunction(ddddddddddddddddddddddddddddddddddd, ddddddddddddddddddddddddddddd), test) {}
diff --git a/src/gn/format_test_data/023.golden b/src/gn/format_test_data/023.golden
new file mode 100644 (file)
index 0000000..bab6e45
--- /dev/null
@@ -0,0 +1,88 @@
+f(aaaaaaaaaaaaaaaaaaa) {
+}
+
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa) {
+}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaaaaaaaaaaaa, aaaaaaaaaaaaa, aaaaaa) {
+}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaa,
+  aaaaa) {
+}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaa,
+                                    aaaaaaaaa) {
+}
+
+aaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaa + aaaaaaaaaa.aaaaaaaaaaaaaaa) {
+}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaa) {
+}
+
+aaaaaaa(aaaaaaaaaaaaa,
+        aaaaaaaaaaaaa,
+        aaaaaaaaaaaaa(aaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaa)) {
+}
+
+# 80 ---------------------------------------------------------------------------
+f(aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaa,
+  aaaaaaaaaaaaaaaaaaaaaa +
+      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaa,
+             aaaaaaaaaaaa,
+             aaaaaaaaaaaa) {
+}
+
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaa(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
+                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+             aaaaaaaaaaaa,
+             aaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaa) {
+}
+
+aaaaaaaaaaaa(
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+    aaaaaaaaaaaa) {
+}
+
+somefunction(someotherFunction(ddddddddddddddddddddddddddddddddddd,
+                               ddddddddddddddddddddddddddddd),
+             test) {
+}
diff --git a/src/gn/format_test_data/024.gn b/src/gn/format_test_data/024.gn
new file mode 100644 (file)
index 0000000..5034cdc
--- /dev/null
@@ -0,0 +1 @@
+somefunc(){}
diff --git a/src/gn/format_test_data/024.golden b/src/gn/format_test_data/024.golden
new file mode 100644 (file)
index 0000000..f2c755d
--- /dev/null
@@ -0,0 +1,2 @@
+somefunc() {
+}
diff --git a/src/gn/format_test_data/025.gn b/src/gn/format_test_data/025.gn
new file mode 100644 (file)
index 0000000..959ec8a
--- /dev/null
@@ -0,0 +1,5 @@
+# Various parenthesis maintenance/trimming.
+if ((a.b && c[d] + ((x < 4 || ((z + b)))))) {
+  y = z - (y - (x - !(b-d)))
+  a += ["a", "b", "c"] - (["x"] - ["y"])
+}
diff --git a/src/gn/format_test_data/025.golden b/src/gn/format_test_data/025.golden
new file mode 100644 (file)
index 0000000..e6d0869
--- /dev/null
@@ -0,0 +1,9 @@
+# Various parenthesis maintenance/trimming.
+if (a.b && c[d] + (x < 4 || z + b)) {
+  y = z - (y - (x - !(b - d)))
+  a += [
+         "a",
+         "b",
+         "c",
+       ] - ([ "x" ] - [ "y" ])
+}
diff --git a/src/gn/format_test_data/026.gn b/src/gn/format_test_data/026.gn
new file mode 100644 (file)
index 0000000..8cf2028
--- /dev/null
@@ -0,0 +1,6 @@
+# 80 ---------------------------------------------------------------------------
+args = [
+  rebase_path("$target_gen_dir/experimental-libraries.cc", root_build_dir),
+  "EXPERIMENTAL",
+  v8_compress_startup_data
+] + rebase_path(sources, root_build_dir)
diff --git a/src/gn/format_test_data/026.golden b/src/gn/format_test_data/026.golden
new file mode 100644 (file)
index 0000000..a1d1d3f
--- /dev/null
@@ -0,0 +1,7 @@
+# 80 ---------------------------------------------------------------------------
+args =
+    [
+      rebase_path("$target_gen_dir/experimental-libraries.cc", root_build_dir),
+      "EXPERIMENTAL",
+      v8_compress_startup_data,
+    ] + rebase_path(sources, root_build_dir)
diff --git a/src/gn/format_test_data/027.gn b/src/gn/format_test_data/027.gn
new file mode 100644 (file)
index 0000000..cc5fe5f
--- /dev/null
@@ -0,0 +1,3 @@
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = [
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaa"]
diff --git a/src/gn/format_test_data/027.golden b/src/gn/format_test_data/027.golden
new file mode 100644 (file)
index 0000000..05f0eb5
--- /dev/null
@@ -0,0 +1,5 @@
+# 80 ---------------------------------------------------------------------------
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = [
+  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "aaaaaaaaaaaaaaaaaaaaaaaaa",
+]
diff --git a/src/gn/format_test_data/028.gn b/src/gn/format_test_data/028.gn
new file mode 100644 (file)
index 0000000..d84e1f8
--- /dev/null
@@ -0,0 +1,9 @@
+# Don't separate these.
+import("wee.gni")
+import("waa.gni")
+
+import("woo.gni")
+
+
+
+import("blah.gni")
diff --git a/src/gn/format_test_data/028.golden b/src/gn/format_test_data/028.golden
new file mode 100644 (file)
index 0000000..9506896
--- /dev/null
@@ -0,0 +1,7 @@
+# Don't separate these.
+import("waa.gni")
+import("wee.gni")
+
+import("woo.gni")
+
+import("blah.gni")
diff --git a/src/gn/format_test_data/029.gn b/src/gn/format_test_data/029.gn
new file mode 100644 (file)
index 0000000..ac67830
--- /dev/null
@@ -0,0 +1,9 @@
+# Don't separate small simple statements.
+is_android = false
+is_chromeos = false
+is_ios = false
+is_linux -= false
+is_mac = true
+is_nacl = false
+is_posix += true
+is_win = false
diff --git a/src/gn/format_test_data/029.golden b/src/gn/format_test_data/029.golden
new file mode 100644 (file)
index 0000000..ac67830
--- /dev/null
@@ -0,0 +1,9 @@
+# Don't separate small simple statements.
+is_android = false
+is_chromeos = false
+is_ios = false
+is_linux -= false
+is_mac = true
+is_nacl = false
+is_posix += true
+is_win = false
diff --git a/src/gn/format_test_data/030.gn b/src/gn/format_test_data/030.gn
new file mode 100644 (file)
index 0000000..adac9a8
--- /dev/null
@@ -0,0 +1,12 @@
+# Don't separate simple statements in a scope.
+
+import("//testing/test.gni")
+
+test("something") {
+  if (is_linux) {
+    sources -= [ "file_version_info_unittest.cc" ]
+    sources += [ "nix/xdg_util_unittest.cc" ]
+    defines = [ "USE_SYMBOLIZE" ]
+    configs += [ "//build/config/linux:glib" ]
+  }
+}
diff --git a/src/gn/format_test_data/030.golden b/src/gn/format_test_data/030.golden
new file mode 100644 (file)
index 0000000..adac9a8
--- /dev/null
@@ -0,0 +1,12 @@
+# Don't separate simple statements in a scope.
+
+import("//testing/test.gni")
+
+test("something") {
+  if (is_linux) {
+    sources -= [ "file_version_info_unittest.cc" ]
+    sources += [ "nix/xdg_util_unittest.cc" ]
+    defines = [ "USE_SYMBOLIZE" ]
+    configs += [ "//build/config/linux:glib" ]
+  }
+}
diff --git a/src/gn/format_test_data/031.gn b/src/gn/format_test_data/031.gn
new file mode 100644 (file)
index 0000000..d83d423
--- /dev/null
@@ -0,0 +1,8 @@
+deps += [
+    ":packed_extra_resources",
+    ":packed_resources",
+
+    # This shouldn't crash.
+
+    # This shouldn't crash 2.
+  ]
diff --git a/src/gn/format_test_data/031.golden b/src/gn/format_test_data/031.golden
new file mode 100644 (file)
index 0000000..6653951
--- /dev/null
@@ -0,0 +1,8 @@
+deps += [
+  ":packed_extra_resources",
+  ":packed_resources",
+
+  # This shouldn't crash.
+
+  # This shouldn't crash 2.
+]
diff --git a/src/gn/format_test_data/032.gn b/src/gn/format_test_data/032.gn
new file mode 100644 (file)
index 0000000..d7ea7e5
--- /dev/null
@@ -0,0 +1,6 @@
+# Make sure continued conditions are aligned.
+if (something) {
+  if (false) {
+  } else if (is_linux && !is_android && current_cpu == "x64" && !disable_iterator_debugging) {
+  }
+}
diff --git a/src/gn/format_test_data/032.golden b/src/gn/format_test_data/032.golden
new file mode 100644 (file)
index 0000000..aeca896
--- /dev/null
@@ -0,0 +1,7 @@
+# Make sure continued conditions are aligned.
+if (something) {
+  if (false) {
+  } else if (is_linux && !is_android && current_cpu == "x64" &&
+             !disable_iterator_debugging) {
+  }
+}
diff --git a/src/gn/format_test_data/033.gn b/src/gn/format_test_data/033.gn
new file mode 100644 (file)
index 0000000..6767acd
--- /dev/null
@@ -0,0 +1,8 @@
+# Don't attach trailing comments too far back.
+if (!is_android) {
+  source_set("tcmalloc") {
+    if (is_win) {
+      ldflags = [ "/ignore:4006:4221" ]
+    }  # is_win
+  }  # source_set
+}  # !is_android
diff --git a/src/gn/format_test_data/033.golden b/src/gn/format_test_data/033.golden
new file mode 100644 (file)
index 0000000..6767acd
--- /dev/null
@@ -0,0 +1,8 @@
+# Don't attach trailing comments too far back.
+if (!is_android) {
+  source_set("tcmalloc") {
+    if (is_win) {
+      ldflags = [ "/ignore:4006:4221" ]
+    }  # is_win
+  }  # source_set
+}  # !is_android
diff --git a/src/gn/format_test_data/034.gn b/src/gn/format_test_data/034.gn
new file mode 100644 (file)
index 0000000..33f5ead
--- /dev/null
@@ -0,0 +1,13 @@
+# Special case for 'args': If args[N] starts with '-' and args[N+1] is a call to
+# rebase_path, keep them as a pair, rather than breaking into individual items.
+action("wee") {
+  if (something) {
+    args = [
+      "--depfile", rebase_path(depfile, root_build_dir),
+      "--android-sdk", rebase_path(android_sdk, root_build_dir),
+      "--android-sdk-tools",
+          rebase_path(android_sdk_build_tools, root_build_dir),
+      "--android-manifest", rebase_path(android_manifest, root_build_dir),
+    ]
+  }
+}
diff --git a/src/gn/format_test_data/035.gn b/src/gn/format_test_data/035.gn
new file mode 100644 (file)
index 0000000..70bc1a9
--- /dev/null
@@ -0,0 +1 @@
+import("//build/config/sysroot.gni")  # Imports android/config.gni.
diff --git a/src/gn/format_test_data/035.golden b/src/gn/format_test_data/035.golden
new file mode 100644 (file)
index 0000000..70bc1a9
--- /dev/null
@@ -0,0 +1 @@
+import("//build/config/sysroot.gni")  # Imports android/config.gni.
diff --git a/src/gn/format_test_data/036.gn b/src/gn/format_test_data/036.gn
new file mode 100644 (file)
index 0000000..5a5eca8
--- /dev/null
@@ -0,0 +1,9 @@
+import("a")
+import("b")
+
+assert(x)
+assert(y)
+assert(z)
+
+source_set("stuff") {
+}
diff --git a/src/gn/format_test_data/036.golden b/src/gn/format_test_data/036.golden
new file mode 100644 (file)
index 0000000..5a5eca8
--- /dev/null
@@ -0,0 +1,9 @@
+import("a")
+import("b")
+
+assert(x)
+assert(y)
+assert(z)
+
+source_set("stuff") {
+}
diff --git a/src/gn/format_test_data/037.gn b/src/gn/format_test_data/037.gn
new file mode 100644 (file)
index 0000000..ebbf0f8
--- /dev/null
@@ -0,0 +1,5 @@
+if (true) {
+  if (true) {
+      args = rebase_path(sources, root_build_dir) + rebase_path(outputs, root_build_dir)
+  }
+}
diff --git a/src/gn/format_test_data/037.golden b/src/gn/format_test_data/037.golden
new file mode 100644 (file)
index 0000000..71e95a4
--- /dev/null
@@ -0,0 +1,6 @@
+if (true) {
+  if (true) {
+    args = rebase_path(sources, root_build_dir) +
+           rebase_path(outputs, root_build_dir)
+  }
+}
diff --git a/src/gn/format_test_data/038.gn b/src/gn/format_test_data/038.gn
new file mode 100644 (file)
index 0000000..ea06d93
--- /dev/null
@@ -0,0 +1,4 @@
+if (stuff) {
+  # Blank line at EOF.
+}
+
diff --git a/src/gn/format_test_data/038.golden b/src/gn/format_test_data/038.golden
new file mode 100644 (file)
index 0000000..f8c9a1b
--- /dev/null
@@ -0,0 +1,3 @@
+if (stuff) {
+  # Blank line at EOF.
+}
diff --git a/src/gn/format_test_data/039.gn b/src/gn/format_test_data/039.gn
new file mode 100644 (file)
index 0000000..662b8cd
--- /dev/null
@@ -0,0 +1,6 @@
+if (true) {
+  assert(arm_float_abi == "" ||
+         arm_float_abi == "hard" ||
+         arm_float_abi == "soft" ||
+         arm_float_abi == "softfp")
+}
diff --git a/src/gn/format_test_data/039.golden b/src/gn/format_test_data/039.golden
new file mode 100644 (file)
index 0000000..b92b1ae
--- /dev/null
@@ -0,0 +1,4 @@
+if (true) {
+  assert(arm_float_abi == "" || arm_float_abi == "hard" ||
+         arm_float_abi == "soft" || arm_float_abi == "softfp")
+}
diff --git a/src/gn/format_test_data/040.gn b/src/gn/format_test_data/040.gn
new file mode 100644 (file)
index 0000000..63e5b07
--- /dev/null
@@ -0,0 +1,9 @@
+# Dewrapping shouldn't cause 80 col to be exceeded.
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (is_win) {
+    cflags = [
+      "/wd4267", # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+    ]
+  }
+}
diff --git a/src/gn/format_test_data/040.golden b/src/gn/format_test_data/040.golden
new file mode 100644 (file)
index 0000000..89fdacf
--- /dev/null
@@ -0,0 +1,10 @@
+# Dewrapping shouldn't cause 80 col to be exceeded.
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (is_win) {
+    cflags = [
+      "/wd4267",  # TODO(jschuh): crbug.com/167187 fix size_t to int
+                  # truncations.
+    ]
+  }
+}
diff --git a/src/gn/format_test_data/041.gn b/src/gn/format_test_data/041.gn
new file mode 100644 (file)
index 0000000..fa39b49
--- /dev/null
@@ -0,0 +1,12 @@
+if (true) {
+  a = [ "wee" ]
+
+  b = [
+    "x",
+    "y",
+    "z",
+  ]
+  c = [ "x" ]
+
+  d = [ "x" ]
+}
diff --git a/src/gn/format_test_data/041.golden b/src/gn/format_test_data/041.golden
new file mode 100644 (file)
index 0000000..fa39b49
--- /dev/null
@@ -0,0 +1,12 @@
+if (true) {
+  a = [ "wee" ]
+
+  b = [
+    "x",
+    "y",
+    "z",
+  ]
+  c = [ "x" ]
+
+  d = [ "x" ]
+}
diff --git a/src/gn/format_test_data/042.gn b/src/gn/format_test_data/042.gn
new file mode 100644 (file)
index 0000000..b827f29
--- /dev/null
@@ -0,0 +1,44 @@
+# Test zero, one, and multiple element for specifically named LHSs.
+if (true) {
+  cflags = []
+  cflags_c = []
+  cflags_cc = []
+  data = []
+  datadeps = []
+  defines = []
+  deps = []
+  include_dirs = []
+  inputs = []
+  ldflags = []
+  outputs = []
+  public_deps = []
+  sources = []
+} else if (true) {
+  cflags = [ "x" ]
+  cflags_c = [ "x" ]
+  cflags_cc = [ "x" ]
+  data = [ "x" ]
+  datadeps = [ "x" ]
+  defines = [ "x" ]
+  deps = [ "x" ]
+  include_dirs = [ "x" ]
+  inputs = [ "x" ]
+  ldflags = [ "x" ]
+  outputs = [ "x" ]
+  public_deps = [ "x" ]
+  sources = [ "x" ]
+} else {
+  cflags = [ "x", "y", "z"]
+  cflags_c = [ "x", "y", "z"]
+  cflags_cc = [ "x", "y", "z"]
+  data = [ "x", "y", "z"]
+  datadeps = [ "x", "y", "z"]
+  defines = [ "x", "y", "z"]
+  deps = [ "x", "y", "z"]
+  include_dirs = [ "x", "y", "z"]
+  inputs = [ "x", "y", "z"]
+  ldflags = [ "x", "y", "z"]
+  outputs = [ "x", "y", "z"]
+  public_deps = [ "x", "y", "z"]
+  sources = [ "x", "y", "z"]
+}
diff --git a/src/gn/format_test_data/042.golden b/src/gn/format_test_data/042.golden
new file mode 100644 (file)
index 0000000..2baca56
--- /dev/null
@@ -0,0 +1,96 @@
+# Test zero, one, and multiple element for specifically named LHSs.
+if (true) {
+  cflags = []
+  cflags_c = []
+  cflags_cc = []
+  data = []
+  datadeps = []
+  defines = []
+  deps = []
+  include_dirs = []
+  inputs = []
+  ldflags = []
+  outputs = []
+  public_deps = []
+  sources = []
+} else if (true) {
+  cflags = [ "x" ]
+  cflags_c = [ "x" ]
+  cflags_cc = [ "x" ]
+  data = [ "x" ]
+  datadeps = [ "x" ]
+  defines = [ "x" ]
+  deps = [ "x" ]
+  include_dirs = [ "x" ]
+  inputs = [ "x" ]
+  ldflags = [ "x" ]
+  outputs = [ "x" ]
+  public_deps = [ "x" ]
+  sources = [ "x" ]
+} else {
+  cflags = [
+    "x",
+    "y",
+    "z",
+  ]
+  cflags_c = [
+    "x",
+    "y",
+    "z",
+  ]
+  cflags_cc = [
+    "x",
+    "y",
+    "z",
+  ]
+  data = [
+    "x",
+    "y",
+    "z",
+  ]
+  datadeps = [
+    "x",
+    "y",
+    "z",
+  ]
+  defines = [
+    "x",
+    "y",
+    "z",
+  ]
+  deps = [
+    "x",
+    "y",
+    "z",
+  ]
+  include_dirs = [
+    "x",
+    "y",
+    "z",
+  ]
+  inputs = [
+    "x",
+    "y",
+    "z",
+  ]
+  ldflags = [
+    "x",
+    "y",
+    "z",
+  ]
+  outputs = [
+    "x",
+    "y",
+    "z",
+  ]
+  public_deps = [
+    "x",
+    "y",
+    "z",
+  ]
+  sources = [
+    "x",
+    "y",
+    "z",
+  ]
+}
diff --git a/src/gn/format_test_data/043.gn b/src/gn/format_test_data/043.gn
new file mode 100644 (file)
index 0000000..b95c6a5
--- /dev/null
@@ -0,0 +1,6 @@
+# Don't break and indent when it's hopeless.
+# 80 ---------------------------------------------------------------------------
+android_java_prebuilt("android_support_v13_java") {
+  jar_path = "$android_sdk_root/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar"
+  jar_path = "$android_sdk_root/extras/android/support/v13/android-support-v13.jar"
+}
diff --git a/src/gn/format_test_data/043.golden b/src/gn/format_test_data/043.golden
new file mode 100644 (file)
index 0000000..336ec2f
--- /dev/null
@@ -0,0 +1,7 @@
+# Don't break and indent when it's hopeless.
+# 80 ---------------------------------------------------------------------------
+android_java_prebuilt("android_support_v13_java") {
+  jar_path = "$android_sdk_root/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar"
+  jar_path =
+      "$android_sdk_root/extras/android/support/v13/android-support-v13.jar"
+}
diff --git a/src/gn/format_test_data/044.gn b/src/gn/format_test_data/044.gn
new file mode 100644 (file)
index 0000000..030c5dd
--- /dev/null
@@ -0,0 +1,11 @@
+# 80 ---------------------------------------------------------------------------
+config("compiler") {
+  if (is_win) {
+    if (is_component_build) {
+      cflags += [
+        "/EHsc",  # Assume C functions can't throw exceptions and don't catch
+                  # structured exceptions (only C++ ones).
+      ]
+    }
+  }
+}
diff --git a/src/gn/format_test_data/044.golden b/src/gn/format_test_data/044.golden
new file mode 100644 (file)
index 0000000..030c5dd
--- /dev/null
@@ -0,0 +1,11 @@
+# 80 ---------------------------------------------------------------------------
+config("compiler") {
+  if (is_win) {
+    if (is_component_build) {
+      cflags += [
+        "/EHsc",  # Assume C functions can't throw exceptions and don't catch
+                  # structured exceptions (only C++ ones).
+      ]
+    }
+  }
+}
diff --git a/src/gn/format_test_data/045.gn b/src/gn/format_test_data/045.gn
new file mode 100644 (file)
index 0000000..28a7280
--- /dev/null
@@ -0,0 +1,10 @@
+static_library("browser") {
+  if (!is_ios) {
+    sources += rebase_path(gypi_values.chrome_browser_predictor_sources,
+                           ".", "//chrome")
+    sources += rebase_path(gypi_values.chrome_browser_predictor_sourcesaaaaaaaa,
+                           ".", "//chrome")
+    sources += rebase_path(gypi_values.chrome_browser_predictor_sourcesaaaaaaaaa,
+                           ".", "//chrome")
+  }
+}
diff --git a/src/gn/format_test_data/045.golden b/src/gn/format_test_data/045.golden
new file mode 100644 (file)
index 0000000..21c560a
--- /dev/null
@@ -0,0 +1,14 @@
+static_library("browser") {
+  if (!is_ios) {
+    sources += rebase_path(gypi_values.chrome_browser_predictor_sources,
+                           ".",
+                           "//chrome")
+    sources += rebase_path(gypi_values.chrome_browser_predictor_sourcesaaaaaaaa,
+                           ".",
+                           "//chrome")
+    sources +=
+        rebase_path(gypi_values.chrome_browser_predictor_sourcesaaaaaaaaa,
+                    ".",
+                    "//chrome")
+  }
+}
diff --git a/src/gn/format_test_data/046.gn b/src/gn/format_test_data/046.gn
new file mode 100644 (file)
index 0000000..28df74b
--- /dev/null
@@ -0,0 +1,22 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  # The JavaScript files required by main.html.
+  remoting_webapp_main_html_js_files =
+      # Include the core files first as it is required by the other files.
+      # Otherwise, Jscompile will complain.
+      remoting_webapp_js_core_files +
+      remoting_webapp_js_auth_client2host_files +
+      remoting_webapp_js_auth_google_files +
+      remoting_webapp_js_client_files +
+      remoting_webapp_js_gnubby_auth_files +
+      remoting_webapp_js_cast_extension_files +
+      remoting_webapp_js_host_files +
+      remoting_webapp_js_logging_files +
+      remoting_webapp_js_ui_files +
+      remoting_webapp_js_ui_host_control_files +
+      remoting_webapp_js_ui_host_display_files +
+      remoting_webapp_js_wcs_container_files
+      # Uncomment this line to include browser test files in the web app
+      # to expedite debugging or local development.
+      #+ remoting_webapp_js_browser_test_files
+}
diff --git a/src/gn/format_test_data/046.golden b/src/gn/format_test_data/046.golden
new file mode 100644 (file)
index 0000000..7d2a679
--- /dev/null
@@ -0,0 +1,19 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  # The JavaScript files required by main.html.
+  remoting_webapp_main_html_js_files =
+      # Include the core files first as it is required by the other files.
+      # Otherwise, Jscompile will complain.
+      remoting_webapp_js_core_files +
+      remoting_webapp_js_auth_client2host_files +
+      remoting_webapp_js_auth_google_files + remoting_webapp_js_client_files +
+      remoting_webapp_js_gnubby_auth_files +
+      remoting_webapp_js_cast_extension_files + remoting_webapp_js_host_files +
+      remoting_webapp_js_logging_files + remoting_webapp_js_ui_files +
+      remoting_webapp_js_ui_host_control_files +
+      remoting_webapp_js_ui_host_display_files +
+      remoting_webapp_js_wcs_container_files
+  # Uncomment this line to include browser test files in the web app
+  # to expedite debugging or local development.
+  #+ remoting_webapp_js_browser_test_files
+}
diff --git a/src/gn/format_test_data/047.gn b/src/gn/format_test_data/047.gn
new file mode 100644 (file)
index 0000000..f1fdbec
--- /dev/null
@@ -0,0 +1,7 @@
+if (true) {
+  args += [ "--template" ] +
+      rebase_path(remoting_webapp_template_files, template_rel_dir)
+  args += [ "--dir-for-templates",
+            rebase_path(template_rel_dir, root_build_dir) ]
+  args += ["--js"] + rebase_path(remoting_webapp_main_html_js_files, template_rel_dir_and_some_more)
+}
diff --git a/src/gn/format_test_data/047.golden b/src/gn/format_test_data/047.golden
new file mode 100644 (file)
index 0000000..5217e76
--- /dev/null
@@ -0,0 +1,10 @@
+if (true) {
+  args += [ "--template" ] +
+          rebase_path(remoting_webapp_template_files, template_rel_dir)
+  args += [
+    "--dir-for-templates",
+    rebase_path(template_rel_dir, root_build_dir),
+  ]
+  args += [ "--js" ] + rebase_path(remoting_webapp_main_html_js_files,
+                                   template_rel_dir_and_some_more)
+}
diff --git a/src/gn/format_test_data/048.gn b/src/gn/format_test_data/048.gn
new file mode 100644 (file)
index 0000000..2584b75
--- /dev/null
@@ -0,0 +1,19 @@
+# No blank inserted after libs (caused by trailing comment on 'else').
+component("google_toolbox_for_mac") {
+  if (!is_ios) {
+    sources += [
+      "src/AddressBook/GTMABAddressBook.h",
+      "src/AddressBook/GTMABAddressBook.m",
+    ]
+
+    frameworks = [
+      "AddressBook.framework",
+      "AppKit.framework",
+    ]
+  } else {  # is_ios
+    sources += [
+      "src/iPhone/GTMFadeTruncatingLabel.h",
+      "src/iPhone/GTMFadeTruncatingLabel.m",
+    ]
+  }
+}
diff --git a/src/gn/format_test_data/048.golden b/src/gn/format_test_data/048.golden
new file mode 100644 (file)
index 0000000..2584b75
--- /dev/null
@@ -0,0 +1,19 @@
+# No blank inserted after libs (caused by trailing comment on 'else').
+component("google_toolbox_for_mac") {
+  if (!is_ios) {
+    sources += [
+      "src/AddressBook/GTMABAddressBook.h",
+      "src/AddressBook/GTMABAddressBook.m",
+    ]
+
+    frameworks = [
+      "AddressBook.framework",
+      "AppKit.framework",
+    ]
+  } else {  # is_ios
+    sources += [
+      "src/iPhone/GTMFadeTruncatingLabel.h",
+      "src/iPhone/GTMFadeTruncatingLabel.m",
+    ]
+  }
+}
diff --git a/src/gn/format_test_data/049.gn b/src/gn/format_test_data/049.gn
new file mode 100644 (file)
index 0000000..fe793d2
--- /dev/null
@@ -0,0 +1,14 @@
+func(aaaaaaaaaaaaaaaaaaaaa,
+     bbbbbbbbbbbbbbbbbbbbbbbbbb,
+     # Comment about function arg.
+     ccccccccccccccccccccccccccccc,
+     dddddddddddddddddddd)
+
+func(aaaaaaaaaaaaaaaaaaaaa,
+     bbbbbbbbbbbbbbbbbbbbbbbbbb,
+
+     # Block comment
+     # Comment about function arg.
+
+     ccccccccccccccccccccccccccccc,
+     dddddddddddddddddddd)
diff --git a/src/gn/format_test_data/050.gn b/src/gn/format_test_data/050.gn
new file mode 100644 (file)
index 0000000..92a6d6d
--- /dev/null
@@ -0,0 +1,10 @@
+# 80 ---------------------------------------------------------------------------
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - cccccccccccccccccccccccc)
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccccc
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccccc + ddddddddddddddddddddd + eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + ffffffffffffff(aaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccc, ddddddddddddddddddd)
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccccc + ddddddddddddddddddddd + eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + ffffffffffffff(aaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccc + ddddddddddddddddddd)
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + cccccccccccccccccccccccc + ddddddddddddddddddddd + eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + ffffffffffffff(aaaaaaaaaaaaa - (bbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccc + ddddddddddddddddddd))
diff --git a/src/gn/format_test_data/050.golden b/src/gn/format_test_data/050.golden
new file mode 100644 (file)
index 0000000..2645600
--- /dev/null
@@ -0,0 +1,27 @@
+# 80 ---------------------------------------------------------------------------
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -
+        (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - cccccccccccccccccccccccc)
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+        cccccccccccccccccccccccc
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+        cccccccccccccccccccccccc + ddddddddddddddddddddd +
+        eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +
+        ffffffffffffff(aaaaaaaaaaaaa,
+                       bbbbbbbbbbbbbbbbbbbbbbbb,
+                       ccccccccccccccccccc,
+                       ddddddddddddddddddd)
+
+zippy = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+        cccccccccccccccccccccccc + ddddddddddddddddddddd +
+        eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +
+        ffffffffffffff(aaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbb +
+                       ccccccccccccccccccc + ddddddddddddddddddd)
+
+zippy =
+    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+    cccccccccccccccccccccccc + ddddddddddddddddddddd +
+    eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +
+    ffffffffffffff(aaaaaaaaaaaaa - (bbbbbbbbbbbbbbbbbbbbbbbb +
+                                    ccccccccccccccccccc + ddddddddddddddddddd))
diff --git a/src/gn/format_test_data/051.gn b/src/gn/format_test_data/051.gn
new file mode 100644 (file)
index 0000000..8076ec6
--- /dev/null
@@ -0,0 +1,6 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa =
+      bbbbbbbbbbbbbbbbbbbb - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+      cccccccccccccccccccccccccccccccccccc
+}
diff --git a/src/gn/format_test_data/051.golden b/src/gn/format_test_data/051.golden
new file mode 100644 (file)
index 0000000..37a1212
--- /dev/null
@@ -0,0 +1,7 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa =
+      bbbbbbbbbbbbbbbbbbbb -
+      bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +
+      cccccccccccccccccccccccccccccccccccc
+}
diff --git a/src/gn/format_test_data/052.gn b/src/gn/format_test_data/052.gn
new file mode 100644 (file)
index 0000000..1f6a10f
--- /dev/null
@@ -0,0 +1,11 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (true) {
+    sources += rebase_path(
+        gypi_values.browser_chromeos_non_athena_sources,
+        ".", "//chrome") +
+        rebase_path(gypi_values.browser_chromeos_extension_non_athena_sources,
+                    ".", "//chrome")
+  }
+}
+
diff --git a/src/gn/format_test_data/052.golden b/src/gn/format_test_data/052.golden
new file mode 100644 (file)
index 0000000..880c563
--- /dev/null
@@ -0,0 +1,12 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (true) {
+    sources +=
+        rebase_path(gypi_values.browser_chromeos_non_athena_sources,
+                    ".",
+                    "//chrome") +
+        rebase_path(gypi_values.browser_chromeos_extension_non_athena_sources,
+                    ".",
+                    "//chrome")
+  }
+}
diff --git a/src/gn/format_test_data/053.gn b/src/gn/format_test_data/053.gn
new file mode 100644 (file)
index 0000000..42a3e08
--- /dev/null
@@ -0,0 +1,7 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  check_internal_result = exec_script(
+    "build/check_internal.py",
+    [ rebase_path("internal/google_chrome_api_keys.h", root_build_dir) ],
+    "value")
+}
diff --git a/src/gn/format_test_data/053.golden b/src/gn/format_test_data/053.golden
new file mode 100644 (file)
index 0000000..76179cc
--- /dev/null
@@ -0,0 +1,8 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  check_internal_result =
+      exec_script("build/check_internal.py",
+                  [ rebase_path("internal/google_chrome_api_keys.h",
+                                root_build_dir) ],
+                  "value")
+}
diff --git a/src/gn/format_test_data/054.gn b/src/gn/format_test_data/054.gn
new file mode 100644 (file)
index 0000000..dca2ace
--- /dev/null
@@ -0,0 +1,7 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  args = [
+    "{{source}}",
+    rebase_path("${target_gen_dir}/{{source_name_part}}-inc.cc", root_build_dir)
+  ]
+}
diff --git a/src/gn/format_test_data/054.golden b/src/gn/format_test_data/054.golden
new file mode 100644 (file)
index 0000000..7dfe5be
--- /dev/null
@@ -0,0 +1,8 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  args = [
+    "{{source}}",
+    rebase_path("${target_gen_dir}/{{source_name_part}}-inc.cc",
+                root_build_dir),
+  ]
+}
diff --git a/src/gn/format_test_data/055.gn b/src/gn/format_test_data/055.gn
new file mode 100644 (file)
index 0000000..7e46724
--- /dev/null
@@ -0,0 +1,10 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (true) {
+    use_system_harfbuzz = exec_script(
+        pkg_config_script,
+        pkg_config_args + [ "--atleast-version=1.31.0", "pangoft2" ],
+        "value")
+  }
+}
+
diff --git a/src/gn/format_test_data/055.golden b/src/gn/format_test_data/055.golden
new file mode 100644 (file)
index 0000000..b2bfdf2
--- /dev/null
@@ -0,0 +1,11 @@
+# 80 ---------------------------------------------------------------------------
+if (true) {
+  if (true) {
+    use_system_harfbuzz = exec_script(pkg_config_script,
+                                      pkg_config_args + [
+                                            "--atleast-version=1.31.0",
+                                            "pangoft2",
+                                          ],
+                                      "value")
+  }
+}
diff --git a/src/gn/format_test_data/056.gn b/src/gn/format_test_data/056.gn
new file mode 100644 (file)
index 0000000..2082a77
--- /dev/null
@@ -0,0 +1,45 @@
+# 80 ---------------------------------------------------------------------------
+java_files = [
+    "test/android/java/src/org/chromium/base/ContentUriTestUtils.java"
+]
+
+defines = [
+    "test/android/java/src/org/chromium/base/ContentUriTestUtils.java"
+]
+
+defines = [
+    "abc/test/android/java/src/org/chromium/base/ContentUriTestUtils.java"
+]
+
+cflags += [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+sources = [ "/a", "/b", "/c" ]
+
+sources = [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+sources += [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+configs -= [
+  # Something!
+  "//build/config/win:nominmax",
+]
+
+cflags = [
+  "/wd4267", # size_t -> int
+  "/wd4324", # structure was padded
+]
diff --git a/src/gn/format_test_data/056.golden b/src/gn/format_test_data/056.golden
new file mode 100644 (file)
index 0000000..df088fc
--- /dev/null
@@ -0,0 +1,45 @@
+# 80 ---------------------------------------------------------------------------
+java_files =
+    [ "test/android/java/src/org/chromium/base/ContentUriTestUtils.java" ]
+
+defines = [ "test/android/java/src/org/chromium/base/ContentUriTestUtils.java" ]
+
+defines =
+    [ "abc/test/android/java/src/org/chromium/base/ContentUriTestUtils.java" ]
+
+cflags += [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+sources = [
+  "/a",
+  "/b",
+  "/c",
+]
+
+sources = [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+sources += [
+  # WEE
+  "/a",
+  "/b",
+  "/c",
+]
+
+configs -= [
+  # Something!
+  "//build/config/win:nominmax",
+]
+
+cflags = [
+  "/wd4267",  # size_t -> int
+  "/wd4324",  # structure was padded
+]
diff --git a/src/gn/format_test_data/057.gn b/src/gn/format_test_data/057.gn
new file mode 100644 (file)
index 0000000..858e311
--- /dev/null
@@ -0,0 +1,24 @@
+# 80 ---------------------------------------------------------------------------
+# Because there is a difference in precedence level between || and &&
+#   a || b || c && d
+# is equivalent to
+#   a || b || (c && d)
+# Because parens are not stored in the parse tree, the formatter recreates the
+# minimally required set to maintain meaning. However, this particular case can
+# be confusing for human readers, so we special case these ones and add
+# strictly-unnecessary parens.
+
+supports_android = (is_apk || is_android_resources ||
+    (is_java_library && defined(invoker.supports_android) &&
+      invoker.supports_android))
+
+enable_one_click_signin = is_win || is_mac || (is_linux && !is_chromeos)
+enable_one_click_signin = (is_linux && !is_chromeos) || is_win || is_mac
+
+x = c || (a&&b)
+x = (a&&b) || c
+x = a&&b || c
+
+x = c && (a||b)
+x = (a||b) && c
+x = a||b && c
diff --git a/src/gn/format_test_data/057.golden b/src/gn/format_test_data/057.golden
new file mode 100644 (file)
index 0000000..d0daa6c
--- /dev/null
@@ -0,0 +1,24 @@
+# 80 ---------------------------------------------------------------------------
+# Because there is a difference in precedence level between || and &&
+#   a || b || c && d
+# is equivalent to
+#   a || b || (c && d)
+# Because parens are not stored in the parse tree, the formatter recreates the
+# minimally required set to maintain meaning. However, this particular case can
+# be confusing for human readers, so we special case these ones and add
+# strictly-unnecessary parens.
+
+supports_android = is_apk || is_android_resources ||
+                   (is_java_library && defined(invoker.supports_android) &&
+                    invoker.supports_android)
+
+enable_one_click_signin = is_win || is_mac || (is_linux && !is_chromeos)
+enable_one_click_signin = (is_linux && !is_chromeos) || is_win || is_mac
+
+x = c || (a && b)
+x = (a && b) || c
+x = (a && b) || c
+
+x = c && (a || b)
+x = (a || b) && c
+x = a || (b && c)
diff --git a/src/gn/format_test_data/058.gn b/src/gn/format_test_data/058.gn
new file mode 100644 (file)
index 0000000..568074a
--- /dev/null
@@ -0,0 +1,2 @@
+if (!defined(invoker.ignore_libs) || !invoker.ignore_libs) {
+}
diff --git a/src/gn/format_test_data/058.golden b/src/gn/format_test_data/058.golden
new file mode 100644 (file)
index 0000000..568074a
--- /dev/null
@@ -0,0 +1,2 @@
+if (!defined(invoker.ignore_libs) || !invoker.ignore_libs) {
+}
diff --git a/src/gn/format_test_data/059.gn b/src/gn/format_test_data/059.gn
new file mode 100644 (file)
index 0000000..ea6fb8e
--- /dev/null
@@ -0,0 +1,10 @@
+assert(type == "android_apk" || type == "java_library" ||
+           type == "android_resources" || things == stuff && stuff != 432)
+
+assert(type == "android_apk" || type == "java_library" ||
+       type == "android_resources",
+       type == "android_apk" || type == "java_library" ||
+       type == "android_resources")
+
+
+if (type == "android_apk" || type == "java_library" || type == "android_resources" || things == stuff && stuff != 432) {}
diff --git a/src/gn/format_test_data/059.golden b/src/gn/format_test_data/059.golden
new file mode 100644 (file)
index 0000000..423e888
--- /dev/null
@@ -0,0 +1,11 @@
+assert(type == "android_apk" || type == "java_library" ||
+       type == "android_resources" || (things == stuff && stuff != 432))
+
+assert(type == "android_apk" || type == "java_library" ||
+           type == "android_resources",
+       type == "android_apk" || type == "java_library" ||
+           type == "android_resources")
+
+if (type == "android_apk" || type == "java_library" ||
+    type == "android_resources" || (things == stuff && stuff != 432)) {
+}
diff --git a/src/gn/format_test_data/060.gn b/src/gn/format_test_data/060.gn
new file mode 100644 (file)
index 0000000..2b0da79
--- /dev/null
@@ -0,0 +1,2 @@
+some_variable = "this is a very long string that is going to exceed 80 col and will never under any circumstance fit"
+another_variable = [ "this is a very long string that is going to exceed 80 col and will never under any circumstance fit" ]
diff --git a/src/gn/format_test_data/060.golden b/src/gn/format_test_data/060.golden
new file mode 100644 (file)
index 0000000..2b0da79
--- /dev/null
@@ -0,0 +1,2 @@
+some_variable = "this is a very long string that is going to exceed 80 col and will never under any circumstance fit"
+another_variable = [ "this is a very long string that is going to exceed 80 col and will never under any circumstance fit" ]
diff --git a/src/gn/format_test_data/061.gn b/src/gn/format_test_data/061.gn
new file mode 100644 (file)
index 0000000..5948037
--- /dev/null
@@ -0,0 +1,9 @@
+action("generate_gl_bindings") {
+  args = [
+    "--header-paths=" + rebase_path("//third_party/khronos", root_build_dir) +
+    ":" + rebase_path("//third_party/mesa/src/include", root_build_dir) + ":" +
+    rebase_path("//ui/gl", root_build_dir) + ":" +
+        rebase_path("//gpu", root_build_dir),
+    rebase_path(gl_binding_output_dir, root_build_dir),
+  ]
+}
diff --git a/src/gn/format_test_data/061.golden b/src/gn/format_test_data/061.golden
new file mode 100644 (file)
index 0000000..edbf43d
--- /dev/null
@@ -0,0 +1,9 @@
+action("generate_gl_bindings") {
+  args = [
+    "--header-paths=" + rebase_path("//third_party/khronos", root_build_dir) +
+        ":" + rebase_path("//third_party/mesa/src/include", root_build_dir) +
+        ":" + rebase_path("//ui/gl", root_build_dir) + ":" +
+        rebase_path("//gpu", root_build_dir),
+    rebase_path(gl_binding_output_dir, root_build_dir),
+  ]
+}
diff --git a/src/gn/format_test_data/062.gn b/src/gn/format_test_data/062.gn
new file mode 100644 (file)
index 0000000..c400591
--- /dev/null
@@ -0,0 +1,128 @@
+# Sorting, making sure we don't detach comments.
+
+sources = []
+
+sources = ["x.cc"]
+
+sources = [
+  "/a",
+  "/b",
+  "/c",
+  # End of block.
+]
+
+sources += [
+  # Start of block, separate.
+
+  "c",
+  "a",
+  "b",
+]
+
+sources += [
+  "z",
+  "z2",
+  # Attached comment.
+  "y.h",
+  "y.cc",
+  "y.mm",
+  "y.rc",
+  "a"
+]
+
+sources += [
+  "z",
+  "z2",
+
+  # Block comment.
+
+  "y.h",
+  "y.cc",
+  "y.mm",
+  "y.rc",
+  "a"
+]
+
+sources += [
+  "z",
+  "z2",
+
+  #
+  # Multiline block comment.
+  #
+
+  "y.h",
+  "y.cc",
+  "y.mm",
+  "y.rc",
+  "a"
+]
+
+# With identifiers.
+sources += [
+  "a",
+  "b",
+  "c",
+  some_other_thing,
+  abcd,
+]
+
+# With accessors.
+sources += [
+  "a",
+  wee[0],
+  "b",
+  invoker.stuff,
+  "c",
+]
+
+# Various separated blocks.
+sources -= [
+  # Fix this test to build on Windows.
+  "focus_cycler_unittest.cc",
+
+  # All tests for multiple displays: not supported on Windows Ash.
+  "wm/drag_window_resizer_unittest.cc",
+
+  # Accelerometer is only available on Chrome OS.
+  "wm/maximize_mode/maximize_mode_controller_unittest.cc",
+
+  # Can't resize on Windows Ash. http://crbug.com/165962
+  "autoclick/autoclick_unittest.cc",
+  "magnifier/magnification_controller_unittest.cc",
+  # Attached 1.
+  # Attached 2.
+  "wm/workspace/workspace_window_resizer_unittest.cc",
+  "sticky_keys/sticky_keys_overlay_unittest.cc",
+  "system/tray/media_security/multi_profile_media_tray_item_unittest.cc",
+  "virtual_keyboard_controller_unittest.cc",
+
+  # Separated at end.
+  "zzzzzzzzzzzzzz.cc",
+]
+
+sources += [
+  "srtp/crypto/include/xfm.h",
+
+  # sources
+  "srtp/srtp/ekt.c",
+  "srtp/srtp/srtp.c",
+  "srtp/crypto/rng/prng.c",
+  "srtp/crypto/rng/rand_source.c",
+]
+
+# Try "public" too. It should be treated the same.
+public = [
+  # Let's sort
+  "this", "into", "word", "salad",
+
+  # But leave
+  "these", "two"
+  # alone!
+]
+
+# Lists with "sources" suffix should also be sorted.
+foo_sources = [
+  "z",
+  "a",
+]
diff --git a/src/gn/format_test_data/062.golden b/src/gn/format_test_data/062.golden
new file mode 100644 (file)
index 0000000..3d653ce
--- /dev/null
@@ -0,0 +1,136 @@
+# Sorting, making sure we don't detach comments.
+
+sources = []
+
+sources = [ "x.cc" ]
+
+sources = [
+  "/a",
+  "/b",
+  "/c",
+
+  # End of block.
+]
+
+sources += [
+  # Start of block, separate.
+
+  "a",
+  "b",
+  "c",
+]
+
+sources += [
+  "a",
+  "y.cc",
+
+  # Attached comment.
+  "y.h",
+  "y.mm",
+  "y.rc",
+  "z",
+  "z2",
+]
+
+sources += [
+  "z",
+  "z2",
+
+  # Block comment.
+
+  "a",
+  "y.cc",
+  "y.h",
+  "y.mm",
+  "y.rc",
+]
+
+sources += [
+  "z",
+  "z2",
+
+  #
+  # Multiline block comment.
+  #
+
+  "a",
+  "y.cc",
+  "y.h",
+  "y.mm",
+  "y.rc",
+]
+
+# With identifiers.
+sources += [
+  "a",
+  "b",
+  "c",
+  abcd,
+  some_other_thing,
+]
+
+# With accessors.
+sources += [
+  "a",
+  "b",
+  "c",
+  invoker.stuff,
+  wee[0],
+]
+
+# Various separated blocks.
+sources -= [
+  # Fix this test to build on Windows.
+  "focus_cycler_unittest.cc",
+
+  # All tests for multiple displays: not supported on Windows Ash.
+  "wm/drag_window_resizer_unittest.cc",
+
+  # Accelerometer is only available on Chrome OS.
+  "wm/maximize_mode/maximize_mode_controller_unittest.cc",
+
+  # Can't resize on Windows Ash. http://crbug.com/165962
+  "autoclick/autoclick_unittest.cc",
+  "magnifier/magnification_controller_unittest.cc",
+  "sticky_keys/sticky_keys_overlay_unittest.cc",
+  "system/tray/media_security/multi_profile_media_tray_item_unittest.cc",
+  "virtual_keyboard_controller_unittest.cc",
+
+  # Attached 1.
+  # Attached 2.
+  "wm/workspace/workspace_window_resizer_unittest.cc",
+
+  # Separated at end.
+  "zzzzzzzzzzzzzz.cc",
+]
+
+sources += [
+  "srtp/crypto/include/xfm.h",
+
+  # sources
+  "srtp/crypto/rng/prng.c",
+  "srtp/crypto/rng/rand_source.c",
+  "srtp/srtp/ekt.c",
+  "srtp/srtp/srtp.c",
+]
+
+# Try "public" too. It should be treated the same.
+public = [
+  # Let's sort
+  "into",
+  "salad",
+  "this",
+  "word",
+
+  # But leave
+  "these",
+  "two",
+
+  # alone!
+]
+
+# Lists with "sources" suffix should also be sorted.
+foo_sources = [
+  "a",
+  "z",
+]
diff --git a/src/gn/format_test_data/063.gn b/src/gn/format_test_data/063.gn
new file mode 100644 (file)
index 0000000..de5002e
--- /dev/null
@@ -0,0 +1,50 @@
+source_set("test") {
+  a = "a"
+  b = "b"
+  deps = [
+    "//a",
+    "//a/a",
+    "//a/b",
+    "//a:a",
+    "//a:b",
+    "//b",
+    ":a",
+    ":b",
+    "a",
+    "a/a",
+    "a/b",
+    "a:a",
+    "a:b",
+    "b",
+    a,
+    b,
+  ]
+
+  public_deps = []
+  if (condition) {
+    public_deps += [
+      "//a",
+      "//a/a",
+      "//a:a",
+      ":a",
+      "a",
+      "a/a",
+      "a:a",
+      a,
+    ]
+  }
+
+  # Sort lists with "deps" suffix as "deps".
+  foo_deps = [
+    "//a",
+    ":z",
+  ]
+
+  # Likewise for visibility
+  visibility = [
+    "//b:*",
+    "//a",
+    "//b/*",
+    ":z",
+  ]
+}
diff --git a/src/gn/format_test_data/063.golden b/src/gn/format_test_data/063.golden
new file mode 100644 (file)
index 0000000..8972886
--- /dev/null
@@ -0,0 +1,50 @@
+source_set("test") {
+  a = "a"
+  b = "b"
+  deps = [
+    ":a",
+    ":b",
+    "a",
+    "a:a",
+    "a:b",
+    "a/a",
+    "a/b",
+    "b",
+    "//a",
+    "//a:a",
+    "//a:b",
+    "//a/a",
+    "//a/b",
+    "//b",
+    a,
+    b,
+  ]
+
+  public_deps = []
+  if (condition) {
+    public_deps += [
+      ":a",
+      "a",
+      "a:a",
+      "a/a",
+      "//a",
+      "//a:a",
+      "//a/a",
+      a,
+    ]
+  }
+
+  # Sort lists with "deps" suffix as "deps".
+  foo_deps = [
+    ":z",
+    "//a",
+  ]
+
+  # Likewise for visibility
+  visibility = [
+    ":z",
+    "//a",
+    "//b:*",
+    "//b/*",
+  ]
+}
diff --git a/src/gn/format_test_data/064.gn b/src/gn/format_test_data/064.gn
new file mode 100644 (file)
index 0000000..c186725
--- /dev/null
@@ -0,0 +1,3 @@
+source_set("test") {
+  deps = [ rebase_path(sdk_dep, ".", mojo_root) ]
+}
diff --git a/src/gn/format_test_data/064.golden b/src/gn/format_test_data/064.golden
new file mode 100644 (file)
index 0000000..c186725
--- /dev/null
@@ -0,0 +1,3 @@
+source_set("test") {
+  deps = [ rebase_path(sdk_dep, ".", mojo_root) ]
+}
diff --git a/src/gn/format_test_data/065.gn b/src/gn/format_test_data/065.gn
new file mode 100644 (file)
index 0000000..a448909
--- /dev/null
@@ -0,0 +1,4 @@
+source_set("test") {
+  some_target_name = ":some_target"
+  deps = [ some_target_name, "//last_target", ":another_target" ]
+}
diff --git a/src/gn/format_test_data/065.golden b/src/gn/format_test_data/065.golden
new file mode 100644 (file)
index 0000000..5df85fd
--- /dev/null
@@ -0,0 +1,8 @@
+source_set("test") {
+  some_target_name = ":some_target"
+  deps = [
+    ":another_target",
+    "//last_target",
+    some_target_name,
+  ]
+}
diff --git a/src/gn/format_test_data/066.gn b/src/gn/format_test_data/066.gn
new file mode 100644 (file)
index 0000000..0a79d0c
--- /dev/null
@@ -0,0 +1,32 @@
+# Suppress sorting based on comment.
+
+# NOSORT
+sources = []
+
+# NOSORT
+sources = [
+  "a",
+]
+
+# NOSORT
+sources += [
+  "a",
+]
+
+# NOSORT
+# This is NOSORT because reason.
+sources = [
+  "z",
+  "z2",
+  "a",
+  "y.cc",
+]
+
+# This is NOSORT because reason:
+# NOSORT
+sources += [
+  "z",
+  "z2",
+  "a",
+  "y.cc",
+]
diff --git a/src/gn/format_test_data/066.golden b/src/gn/format_test_data/066.golden
new file mode 100644 (file)
index 0000000..bdcf055
--- /dev/null
@@ -0,0 +1,28 @@
+# Suppress sorting based on comment.
+
+# NOSORT
+sources = []
+
+# NOSORT
+sources = [ "a" ]
+
+# NOSORT
+sources += [ "a" ]
+
+# NOSORT
+# This is NOSORT because reason.
+sources = [
+  "z",
+  "z2",
+  "a",
+  "y.cc",
+]
+
+# This is NOSORT because reason:
+# NOSORT
+sources += [
+  "z",
+  "z2",
+  "a",
+  "y.cc",
+]
diff --git a/src/gn/format_test_data/067.gn b/src/gn/format_test_data/067.gn
new file mode 100644 (file)
index 0000000..b3d5eaa
--- /dev/null
@@ -0,0 +1,8 @@
+# Scope wrapping.
+
+myscope = {
+}
+myscope = { a = 1 }
+myscope = { a = 1 b = 2}
+# Comment
+SomeFunction({a=1}, "foo")
diff --git a/src/gn/format_test_data/067.golden b/src/gn/format_test_data/067.golden
new file mode 100644 (file)
index 0000000..9ce2fa7
--- /dev/null
@@ -0,0 +1,17 @@
+# Scope wrapping.
+
+myscope = {
+}
+myscope = {
+  a = 1
+}
+myscope = {
+  a = 1
+  b = 2
+}
+
+# Comment
+SomeFunction({
+               a = 1
+             },
+             "foo")
diff --git a/src/gn/format_test_data/068.gn b/src/gn/format_test_data/068.gn
new file mode 100644 (file)
index 0000000..7fb8e1b
--- /dev/null
@@ -0,0 +1,3 @@
+# Initial comment
+
+# Comment that should be separate, no subsequent content in file.
diff --git a/src/gn/format_test_data/068.golden b/src/gn/format_test_data/068.golden
new file mode 100644 (file)
index 0000000..7fb8e1b
--- /dev/null
@@ -0,0 +1,3 @@
+# Initial comment
+
+# Comment that should be separate, no subsequent content in file.
diff --git a/src/gn/format_test_data/069.gn b/src/gn/format_test_data/069.gn
new file mode 100644 (file)
index 0000000..200b313
--- /dev/null
@@ -0,0 +1,3 @@
+if (true) {
+  configs -= [ "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", ]
+}
diff --git a/src/gn/format_test_data/069.golden b/src/gn/format_test_data/069.golden
new file mode 100644 (file)
index 0000000..64d4acc
--- /dev/null
@@ -0,0 +1,5 @@
+if (true) {
+  configs -= [
+    "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors",
+  ]
+}
diff --git a/src/gn/format_test_data/070.gn b/src/gn/format_test_data/070.gn
new file mode 100644 (file)
index 0000000..09d80d2
--- /dev/null
@@ -0,0 +1,15 @@
+multiple = [
+    {
+      name = "elements_test"
+    },
+    {
+      name = "eapol_crypto_test"
+    },
+  ]
+
+
+single = [
+    {
+      name = "elements_test"
+    },
+  ]
diff --git a/src/gn/format_test_data/070.golden b/src/gn/format_test_data/070.golden
new file mode 100644 (file)
index 0000000..0997aa4
--- /dev/null
@@ -0,0 +1,14 @@
+multiple = [
+  {
+    name = "elements_test"
+  },
+  {
+    name = "eapol_crypto_test"
+  },
+]
+
+single = [
+  {
+    name = "elements_test"
+  },
+]
diff --git a/src/gn/format_test_data/071.gn b/src/gn/format_test_data/071.gn
new file mode 100644 (file)
index 0000000..665d099
--- /dev/null
@@ -0,0 +1,26 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("z.gni")
+import("x.gni")
+import("y.gni")
+
+import("b.gni")
+import("a.gni")
+
+
+
+import("m.gni")
+import("d1.gni")
+# Comment here
+import("c1.gni")
+
+import("../something/relative.gni")
+import("//build/stuff.gni")
+import("nopath.gni")
+import("//abc/things.gni")
+
+import("")
+import()
+import("a")
diff --git a/src/gn/format_test_data/071.golden b/src/gn/format_test_data/071.golden
new file mode 100644 (file)
index 0000000..6d8f98f
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("x.gni")
+import("y.gni")
+import("z.gni")
+
+import("a.gni")
+import("b.gni")
+
+import("d1.gni")
+import("m.gni")
+
+# Comment here
+import("c1.gni")
+
+import("//abc/things.gni")
+import("//build/stuff.gni")
+import("../something/relative.gni")
+import("nopath.gni")
+
+import()
+import("")
+import("a")
diff --git a/src/gn/format_test_data/072.gn b/src/gn/format_test_data/072.gn
new file mode 100644 (file)
index 0000000..10b5a81
--- /dev/null
@@ -0,0 +1,8 @@
+import("b") import("c") import("a") import("d")
+
+import("z") declare_args() {} import("y") import("x") import("w")
+
+import("3") import("2") import("1")
+
+import("x") import("y")
+import("z") import("w")
diff --git a/src/gn/format_test_data/072.golden b/src/gn/format_test_data/072.golden
new file mode 100644 (file)
index 0000000..b4e5a4b
--- /dev/null
@@ -0,0 +1,21 @@
+import("a")
+import("b")
+import("c")
+import("d")
+
+import("z")
+declare_args() {
+}
+
+import("w")
+import("x")
+import("y")
+
+import("1")
+import("2")
+import("3")
+
+import("w")
+import("x")
+import("y")
+import("z")
diff --git a/src/gn/format_test_data/073.gn b/src/gn/format_test_data/073.gn
new file mode 100644 (file)
index 0000000..e3a6561
--- /dev/null
@@ -0,0 +1,23 @@
+import("//root/a")
+import("//root/c")
+import("//root/b")
+
+if (stuff) {
+import("3") import("2") import("1")
+  if (things) {
+      import("x")
+      import("z") import("y") import("w") import("q")
+
+      import("f") import("e") if (other) {import("z") import("a")} import("d")
+
+      template("wee") {
+        import("6")
+        import("7")
+        import("5")
+      }
+  }
+} else {
+  import("i")
+  import("h")
+  import("g")
+}
diff --git a/src/gn/format_test_data/073.golden b/src/gn/format_test_data/073.golden
new file mode 100644 (file)
index 0000000..e32977b
--- /dev/null
@@ -0,0 +1,34 @@
+import("//root/a")
+import("//root/b")
+import("//root/c")
+
+if (stuff) {
+  import("1")
+  import("2")
+  import("3")
+  if (things) {
+    import("q")
+    import("w")
+    import("x")
+    import("y")
+    import("z")
+
+    import("e")
+    import("f")
+    if (other) {
+      import("a")
+      import("z")
+    }
+
+    import("d")
+    template("wee") {
+      import("5")
+      import("6")
+      import("7")
+    }
+  }
+} else {
+  import("g")
+  import("h")
+  import("i")
+}
diff --git a/src/gn/format_test_data/074.gn b/src/gn/format_test_data/074.gn
new file mode 100644 (file)
index 0000000..d5e5101
--- /dev/null
@@ -0,0 +1,7 @@
+config("something") {
+  # Makes builds independent of absolute file path.
+  if (symbol_level != 0 && is_clang &&
+      strip_absolute_paths_from_debug_symbols) {
+    print("hi")
+  }
+}
diff --git a/src/gn/format_test_data/074.golden b/src/gn/format_test_data/074.golden
new file mode 100644 (file)
index 0000000..d5e5101
--- /dev/null
@@ -0,0 +1,7 @@
+config("something") {
+  # Makes builds independent of absolute file path.
+  if (symbol_level != 0 && is_clang &&
+      strip_absolute_paths_from_debug_symbols) {
+    print("hi")
+  }
+}
diff --git a/src/gn/format_test_data/075.gn b/src/gn/format_test_data/075.gn
new file mode 100644 (file)
index 0000000..8a3c327
--- /dev/null
@@ -0,0 +1,11 @@
+# This tests for a case where formatting did not reach a fixed point after a
+# single run of formatting.
+
+import("stuff.gni")
+
+# Subprojects need to override arguments in {mac,ios}_sdk_overrides.gni in their
+# .gn config, but those arguments are only used on macOS. Including
+# mac_sdk_overrides.gni insures that this doesn't trigger an unused argument
+# warning.
+import("//build/config/mac/mac_sdk_overrides.gni")
+import("//build/config/ios/ios_sdk_overrides.gni")
diff --git a/src/gn/format_test_data/075.golden b/src/gn/format_test_data/075.golden
new file mode 100644 (file)
index 0000000..d181bd6
--- /dev/null
@@ -0,0 +1,12 @@
+# This tests for a case where formatting did not reach a fixed point after a
+# single run of formatting.
+
+import("stuff.gni")
+
+import("//build/config/ios/ios_sdk_overrides.gni")
+
+# Subprojects need to override arguments in {mac,ios}_sdk_overrides.gni in their
+# .gn config, but those arguments are only used on macOS. Including
+# mac_sdk_overrides.gni insures that this doesn't trigger an unused argument
+# warning.
+import("//build/config/mac/mac_sdk_overrides.gni")
diff --git a/src/gn/format_test_data/076.gn b/src/gn/format_test_data/076.gn
new file mode 100644 (file)
index 0000000..ea617dc
--- /dev/null
@@ -0,0 +1,12 @@
+# Intentionally contains tabs which the formatter should convert to spaces, but
+# only where it's not relevant to the parse.
+
+if (true) {
+       sources = [
+         "a.c", "a.h", "main.c"
+       ]
+}
+
+if (false) {
+  embedded = "a tab    to leave        alone"
+}
diff --git a/src/gn/format_test_data/076.golden b/src/gn/format_test_data/076.golden
new file mode 100644 (file)
index 0000000..b682482
--- /dev/null
@@ -0,0 +1,14 @@
+# Intentionally contains tabs which the formatter should convert to spaces, but
+# only where it's not relevant to the parse.
+
+if (true) {
+  sources = [
+    "a.c",
+    "a.h",
+    "main.c",
+  ]
+}
+
+if (false) {
+  embedded = "a tab    to leave        alone"
+}
diff --git a/src/gn/format_test_data/077.gn b/src/gn/format_test_data/077.gn
new file mode 100644 (file)
index 0000000..665d420
--- /dev/null
@@ -0,0 +1,6 @@
+# Regression test for https://crbug.com/gn/138. 80 col -------------------------
+foo("bar") {
+  blah = [
+    "$target_gen_dir/run-lit",  # Non-existing, so that ninja runs it each time.
+  ]
+}
diff --git a/src/gn/format_test_data/077.golden b/src/gn/format_test_data/077.golden
new file mode 100644 (file)
index 0000000..665d420
--- /dev/null
@@ -0,0 +1,6 @@
+# Regression test for https://crbug.com/gn/138. 80 col -------------------------
+foo("bar") {
+  blah = [
+    "$target_gen_dir/run-lit",  # Non-existing, so that ninja runs it each time.
+  ]
+}
diff --git a/src/gn/format_test_data/078.gn b/src/gn/format_test_data/078.gn
new file mode 100644 (file)
index 0000000..9b1369a
--- /dev/null
@@ -0,0 +1,15 @@
+# 80 ---------------------------------------------------------------------------
+# Long suffix comments, and including trailing spaces.
+config("compiler") {
+  if (is_win) {
+    if (is_component_build) {
+      cflags += [
+        "/EHsc",  # These are some very long suffix comment words aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+                  # bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb   
+                  # cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+                  # dddddddddddddddd ddddddddddd dddddddd ddddddddddddd ddddddddddddddd
+      ]
+    }
+  }
+}
+
diff --git a/src/gn/format_test_data/078.golden b/src/gn/format_test_data/078.golden
new file mode 100644 (file)
index 0000000..ea96580
--- /dev/null
@@ -0,0 +1,16 @@
+# 80 ---------------------------------------------------------------------------
+# Long suffix comments, and including trailing spaces.
+config("compiler") {
+  if (is_win) {
+    if (is_component_build) {
+      cflags += [
+        "/EHsc",  # These are some very long suffix comment words
+                  # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+                  # bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+                  # cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+                  # dddddddddddddddd ddddddddddd dddddddd ddddddddddddd
+                  # ddddddddddddddd
+      ]
+    }
+  }
+}
diff --git a/src/gn/format_test_data/079.gn b/src/gn/format_test_data/079.gn
new file mode 100644 (file)
index 0000000..9218757
--- /dev/null
@@ -0,0 +1,14 @@
+# 80 ---------------------------------------------------------------------------
+# Somewhat more intelligent suffix comments.
+
+core_generated_interface_idl_files = generated_webcore_testing_idl_files  # interfaces
+
+core_generated_interface_idl_files = generated_webcore_testing_idl_files_and_some_more_longer # stuff
+
+if (true) {
+  if (true) {
+    if (true) {
+      dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"  # e.g. foo.dll
+    }
+  }
+}
diff --git a/src/gn/format_test_data/079.golden b/src/gn/format_test_data/079.golden
new file mode 100644 (file)
index 0000000..c4617b5
--- /dev/null
@@ -0,0 +1,18 @@
+# 80 ---------------------------------------------------------------------------
+# Somewhat more intelligent suffix comments.
+
+core_generated_interface_idl_files =
+    generated_webcore_testing_idl_files  # interfaces
+
+core_generated_interface_idl_files =
+    generated_webcore_testing_idl_files_and_some_more_longer  # stuff
+
+if (true) {
+  if (true) {
+    if (true) {
+      dllname =
+          "{{output_dir}}/{{target_output_name}}{{output_extension}}"  # e.g.
+                                                                       # foo.dll
+    }
+  }
+}
diff --git a/src/gn/format_test_data/080.gn b/src/gn/format_test_data/080.gn
new file mode 100644 (file)
index 0000000..3e3fdba
--- /dev/null
@@ -0,0 +1,33 @@
+# https://crbug.com/gn/141. 80 col ---------------------------------------------
+
+a =
+    [ "b" ]  # comment1
+
+a =
+    [ "b", "c" ]  # comment1b
+
+a =
+    b  # comment2
+
+a =
+    { b = 3 }  # comment3
+
+a =
+    { # comment4
+
+      b = 3 }  # comment5
+
+a =
+    { b = 3 # comment6
+    }
+
+if (true) {
+  if (true) {
+    if (true) {
+      something_longer_on_the_lhs =
+          [ "something that will exceed 80 col if dewrapped" ]  # comment7
+
+      something_longer_on_the_lhs = [ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]  # comment8
+    }
+  }
+}
diff --git a/src/gn/format_test_data/080.golden b/src/gn/format_test_data/080.golden
new file mode 100644 (file)
index 0000000..9f18a5e
--- /dev/null
@@ -0,0 +1,34 @@
+# https://crbug.com/gn/141. 80 col ---------------------------------------------
+
+a = [ "b" ]  # comment1
+
+a = [
+  "b",
+  "c",
+]  # comment1b
+
+a = b  # comment2
+
+a = {
+  b = 3
+}  # comment3
+
+a = {  # comment4
+  b = 3
+}  # comment5
+
+a = {
+  b = 3  # comment6
+}
+
+if (true) {
+  if (true) {
+    if (true) {
+      something_longer_on_the_lhs =
+          [ "something that will exceed 80 col if dewrapped" ]  # comment7
+
+      something_longer_on_the_lhs =
+          [ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]  # comment8
+    }
+  }
+}
diff --git a/src/gn/format_test_data/081.gn b/src/gn/format_test_data/081.gn
new file mode 100644 (file)
index 0000000..d9d494e
--- /dev/null
@@ -0,0 +1,18 @@
+inputs = idl_lexer_parser_files + idl_compiler_files # to be explicit (covered by parsetab)
+inputs += "hi"
+
+if (true) {
+  if (true) {
+    inputs = idl_lexer_parser_files + idl_compiler_files # to be explicit (covered by parsetab)
+    inputs += "hi"
+  }
+}
+
+if (true) {
+  if (something) {
+    a = b
+  } else {  # !is_chromeos
+    os_category = current_os
+  }
+  no_blank_here = true
+}
diff --git a/src/gn/format_test_data/081.golden b/src/gn/format_test_data/081.golden
new file mode 100644 (file)
index 0000000..c4ee25f
--- /dev/null
@@ -0,0 +1,21 @@
+inputs = idl_lexer_parser_files + idl_compiler_files  # to be explicit (covered
+                                                      # by parsetab)
+inputs += "hi"
+
+if (true) {
+  if (true) {
+    inputs =
+        idl_lexer_parser_files + idl_compiler_files  # to be explicit (covered
+                                                     # by parsetab)
+    inputs += "hi"
+  }
+}
+
+if (true) {
+  if (something) {
+    a = b
+  } else {  # !is_chromeos
+    os_category = current_os
+  }
+  no_blank_here = true
+}
diff --git a/src/gn/format_test_data/082.gn b/src/gn/format_test_data/082.gn
new file mode 100644 (file)
index 0000000..2ced597
--- /dev/null
@@ -0,0 +1,34 @@
+# https://crbug.com/gn/156 -----------------------------------------------------
+if (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
+    (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ||
+     cccccccccccccccccccccc)) {
+}
+
+if (!is_nacl && !use_libfzzzzuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzer &&
+    (target_os == "android" || target_os == "chromeos" ||
+     target_os == "fuchsia" || target_os == "linux" ||
+     target_os == "winnnnn")) {
+  print("hi")
+}
+
+foo("bar") {
+  if (foo) {
+    if (!is_nacl && !use_libfuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzer &&
+        (target_os == "android" || target_os == "chromeos" ||
+         target_os == "fuchsia" || target_os == "linux" ||
+         target_os == "win")) {
+      cflags += [ "-Wunreachable-code" ]
+    }
+  }
+}
+
+foo("bar") {
+  if (foo) {
+    if (!is_nacl && !use_libfuzzer &&
+        (target_os == "android" || target_os == "chromeos" ||
+         target_os == "fuchsia" || target_os == "linux" ||
+         target_os == "win")) {
+      cflags += [ "-Wunreachable-code" ]
+    }
+  }
+}
diff --git a/src/gn/format_test_data/082.golden b/src/gn/format_test_data/082.golden
new file mode 100644 (file)
index 0000000..2ced597
--- /dev/null
@@ -0,0 +1,34 @@
+# https://crbug.com/gn/156 -----------------------------------------------------
+if (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
+    (bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ||
+     cccccccccccccccccccccc)) {
+}
+
+if (!is_nacl && !use_libfzzzzuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzer &&
+    (target_os == "android" || target_os == "chromeos" ||
+     target_os == "fuchsia" || target_os == "linux" ||
+     target_os == "winnnnn")) {
+  print("hi")
+}
+
+foo("bar") {
+  if (foo) {
+    if (!is_nacl && !use_libfuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzer &&
+        (target_os == "android" || target_os == "chromeos" ||
+         target_os == "fuchsia" || target_os == "linux" ||
+         target_os == "win")) {
+      cflags += [ "-Wunreachable-code" ]
+    }
+  }
+}
+
+foo("bar") {
+  if (foo) {
+    if (!is_nacl && !use_libfuzzer &&
+        (target_os == "android" || target_os == "chromeos" ||
+         target_os == "fuchsia" || target_os == "linux" ||
+         target_os == "win")) {
+      cflags += [ "-Wunreachable-code" ]
+    }
+  }
+}
diff --git a/src/gn/format_test_data/083.gn b/src/gn/format_test_data/083.gn
new file mode 100644 (file)
index 0000000..6936bf5
--- /dev/null
@@ -0,0 +1,7 @@
+# Crash found by afl-fuzz (non-literal import).
+import("x")
+import(sources)
+import(zip)
+import(zap)
+import(a+b+c+d)
+import(zzz+yyy)
diff --git a/src/gn/format_test_data/083.golden b/src/gn/format_test_data/083.golden
new file mode 100644 (file)
index 0000000..445534d
--- /dev/null
@@ -0,0 +1,7 @@
+# Crash found by afl-fuzz (non-literal import).
+import(sources)
+import(zip)
+import(zap)
+import(a + b + c + d)
+import(zzz + yyy)
+import("x")
diff --git a/src/gn/frameworks_utils.cc b/src/gn/frameworks_utils.cc
new file mode 100644 (file)
index 0000000..4aba827
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/frameworks_utils.h"
+
+#include "gn/filesystem_utils.h"
+
+namespace {
+
+// Name of the extension of frameworks.
+const char kFrameworkExtension[] = "framework";
+
+}  // anonymous namespace
+
+std::string_view GetFrameworkName(const std::string& file) {
+  if (FindFilenameOffset(file) != 0)
+    return std::string_view();
+
+  std::string_view extension = FindExtension(&file);
+  if (extension != kFrameworkExtension)
+    return std::string_view();
+
+  return FindFilenameNoExtension(&file);
+}
diff --git a/src/gn/frameworks_utils.h b/src/gn/frameworks_utils.h
new file mode 100644 (file)
index 0000000..b9df3e7
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FRAMEWORKS_UTILS_H_
+#define TOOLS_GN_FRAMEWORKS_UTILS_H_
+
+#include <string>
+#include <string_view>
+
+// Returns the name of the framework from a file name. This returns an empty
+// string_view if the name is incorrect (does not ends in ".framework", ...).
+std::string_view GetFrameworkName(const std::string& file);
+
+#endif  // TOOLS_GN_FRAMEWORKS_UTILS_H_
diff --git a/src/gn/frameworks_utils_unittest.cc b/src/gn/frameworks_utils_unittest.cc
new file mode 100644 (file)
index 0000000..3e20823
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/frameworks_utils.h"
+
+#include "util/test/test.h"
+
+TEST(FrameworksUtils, GetFrameworkName) {
+  const std::string kFramework = "Foundation.framework";
+  const std::string kFrameworkNoExtension = "Foundation";
+  const std::string kFrameworkPath = "Foo/Foo.framework";
+
+  EXPECT_EQ("Foundation", GetFrameworkName(kFramework));
+  EXPECT_EQ("", GetFrameworkName(kFrameworkNoExtension));
+  EXPECT_EQ("", GetFrameworkName(kFrameworkPath));
+}
diff --git a/src/gn/function_exec_script.cc b/src/gn/function_exec_script.cc
new file mode 100644 (file)
index 0000000..f5f24aa
--- /dev/null
@@ -0,0 +1,278 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/err.h"
+#include "gn/exec_process.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/input_conversion.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/trace.h"
+#include "gn/value.h"
+#include "util/build_config.h"
+#include "util/ticks.h"
+
+namespace functions {
+
+namespace {
+
+bool CheckExecScriptPermissions(const BuildSettings* build_settings,
+                                const FunctionCallNode* function,
+                                Err* err) {
+  const SourceFileSet* whitelist = build_settings->exec_script_whitelist();
+  if (!whitelist)
+    return true;  // No whitelist specified, don't check.
+
+  LocationRange function_range = function->GetRange();
+  if (!function_range.begin().file())
+    return true;  // No file, might be some internal thing, implicitly pass.
+
+  if (whitelist->find(function_range.begin().file()->name()) !=
+      whitelist->end())
+    return true;  // Whitelisted, this is OK.
+
+  // Disallowed case.
+  *err = Err(
+      function, "Disallowed exec_script call.",
+      "The use of exec_script use is restricted in this build. exec_script\n"
+      "is discouraged because it can slow down the GN run and is easily\n"
+      "abused.\n"
+      "\n"
+      "Generally nontrivial work should be done as build steps rather than\n"
+      "when GN is run. For example, if you need to compute a nontrivial\n"
+      "preprocessor define, it will be better to have an action target\n"
+      "generate a header containing the define rather than blocking the GN\n"
+      "run to compute the value.\n"
+      "\n"
+      "The allowed callers of exec_script is maintained in the \"//.gn\" file\n"
+      "if you need to modify the whitelist.");
+  return false;
+}
+
+}  // namespace
+
+const char kExecScript[] = "exec_script";
+const char kExecScript_HelpShort[] =
+    "exec_script: Synchronously run a script and return the output.";
+const char kExecScript_Help[] =
+    R"(exec_script: Synchronously run a script and return the output.
+
+  exec_script(filename,
+              arguments = [],
+              input_conversion = "",
+              file_dependencies = [])
+
+  Runs the given script, returning the stdout of the script. The build
+  generation will fail if the script does not exist or returns a nonzero exit
+  code.
+
+  The current directory when executing the script will be the root build
+  directory. If you are passing file names, you will want to use the
+  rebase_path() function to make file names relative to this path (see "gn help
+  rebase_path").
+
+  The default script interpreter is Python ("python" on POSIX, "python.exe" or
+  "python.bat" on Windows). This can be configured by the script_executable
+  variable, see "gn help dotfile".
+
+Arguments:
+
+  filename:
+      File name of script to execute. Non-absolute names will be treated as
+      relative to the current build file.
+
+  arguments:
+      A list of strings to be passed to the script as arguments. May be
+      unspecified or the empty list which means no arguments.
+
+  input_conversion:
+      Controls how the file is read and parsed. See "gn help io_conversion".
+
+      If unspecified, defaults to the empty string which causes the script
+      result to be discarded. exec script will return None.
+
+  dependencies:
+      (Optional) A list of files that this script reads or otherwise depends
+      on. These dependencies will be added to the build result such that if any
+      of them change, the build will be regenerated and the script will be
+      re-run.
+
+      The script itself will be an implicit dependency so you do not need to
+      list it.
+
+Example
+
+  all_lines = exec_script(
+      "myscript.py", [some_input], "list lines",
+      [ rebase_path("data_file.txt", root_build_dir) ])
+
+  # This example just calls the script with no arguments and discards the
+  # result.
+  exec_script("//foo/bar/myscript.py")
+)";
+
+Value RunExecScript(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    Err* err) {
+  if (args.size() < 1 || args.size() > 4) {
+    *err = Err(function->function(), "Wrong number of arguments to exec_script",
+               "I expected between one and four arguments.");
+    return Value();
+  }
+
+  const Settings* settings = scope->settings();
+  const BuildSettings* build_settings = settings->build_settings();
+  const SourceDir& cur_dir = scope->GetSourceDir();
+
+  if (!CheckExecScriptPermissions(build_settings, function, err))
+    return Value();
+
+  // Find the script to run.
+  std::string script_source_path = cur_dir.ResolveRelativeAs(
+      true, args[0], err,
+      scope->settings()->build_settings()->root_path_utf8());
+  if (err->has_error())
+    return Value();
+  base::FilePath script_path =
+      build_settings->GetFullPath(script_source_path, true);
+  if (!build_settings->secondary_source_path().empty() &&
+      !base::PathExists(script_path)) {
+    // Fall back to secondary source root when the file doesn't exist.
+    script_path =
+        build_settings->GetFullPathSecondary(script_source_path, true);
+  }
+
+  ScopedTrace trace(TraceItem::TRACE_SCRIPT_EXECUTE, script_source_path);
+  trace.SetToolchain(settings->toolchain_label());
+
+  // Add all dependencies of this script, including the script itself, to the
+  // build deps.
+  g_scheduler->AddGenDependency(script_path);
+  if (args.size() == 4) {
+    const Value& deps_value = args[3];
+    if (!deps_value.VerifyTypeIs(Value::LIST, err))
+      return Value();
+
+    for (const auto& dep : deps_value.list_value()) {
+      if (!dep.VerifyTypeIs(Value::STRING, err))
+        return Value();
+      g_scheduler->AddGenDependency(build_settings->GetFullPath(
+          cur_dir.ResolveRelativeAs(
+              true, dep, err,
+              scope->settings()->build_settings()->root_path_utf8()),
+          true));
+      if (err->has_error())
+        return Value();
+    }
+  }
+
+  // Make the command line.
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // CommandLine tries to interpret arguments by default.  Disable that so
+  // that the arguments will be passed through exactly as specified.
+  cmdline.SetParseSwitches(false);
+
+  // If an interpreter path is set, initialize it as the first entry and
+  // pass script_path as the first argument. Otherwise, set the
+  // program to script_path directly.
+  const base::FilePath& interpreter_path = build_settings->python_path();
+  if (!interpreter_path.empty()) {
+    cmdline.SetProgram(interpreter_path);
+    cmdline.AppendArgPath(script_path);
+  } else {
+    cmdline.SetProgram(script_path);
+  }
+
+  if (args.size() >= 2) {
+    // Optional command-line arguments to the script.
+    const Value& script_args = args[1];
+    if (!script_args.VerifyTypeIs(Value::LIST, err))
+      return Value();
+    for (const auto& arg : script_args.list_value()) {
+      if (!arg.VerifyTypeIs(Value::STRING, err))
+        return Value();
+      cmdline.AppendArg(arg.string_value());
+    }
+  }
+
+  // Log command line for debugging help.
+  trace.SetCommandLine(cmdline);
+  Ticks begin_exec = 0;
+  if (g_scheduler->verbose_logging()) {
+#if defined(OS_WIN)
+    g_scheduler->Log("Executing",
+                     base::UTF16ToUTF8(cmdline.GetCommandLineString()));
+#else
+    g_scheduler->Log("Executing", cmdline.GetCommandLineString());
+#endif
+    begin_exec = TicksNow();
+  }
+
+  base::FilePath startup_dir =
+      build_settings->GetFullPath(build_settings->build_dir());
+  // The first time a build is run, no targets will have been written so the
+  // build output directory won't exist. We need to make sure it does before
+  // running any scripts with this as its startup directory, although it will
+  // be relatively rare that the directory won't exist by the time we get here.
+  //
+  // If this shows up on benchmarks, we can cache whether we've done this
+  // or not and skip creating the directory.
+  base::CreateDirectory(startup_dir);
+
+  // Execute the process.
+  // TODO(brettw) set the environment block.
+  std::string output;
+  std::string stderr_output;
+  int exit_code = 0;
+  {
+    if (!internal::ExecProcess(cmdline, startup_dir, &output, &stderr_output,
+                               &exit_code)) {
+      *err = Err(
+          function->function(), "Could not execute interpreter.",
+          "I was trying to execute \"" + FilePathToUTF8(interpreter_path) +
+          "\".");
+      return Value();
+    }
+  }
+  if (g_scheduler->verbose_logging()) {
+    g_scheduler->Log(
+        "Executing",
+        script_source_path + " took " +
+            base::Int64ToString(
+                TicksDelta(TicksNow(), begin_exec).InMilliseconds()) +
+            "ms");
+  }
+
+  if (exit_code != 0) {
+    std::string msg =
+        "Current dir: " + FilePathToUTF8(startup_dir) +
+        "\nCommand: " + FilePathToUTF8(cmdline.GetCommandLineString()) +
+        "\nReturned " + base::IntToString(exit_code);
+    if (!output.empty())
+      msg += " and printed out:\n\n" + output;
+    else
+      msg += ".";
+    if (!stderr_output.empty())
+      msg += "\nstderr:\n\n" + stderr_output;
+
+    *err =
+        Err(function->function(), "Script returned non-zero exit code.", msg);
+    return Value();
+  }
+
+  // Default to None value for the input conversion if unspecified.
+  return ConvertInputToValue(scope->settings(), output, function,
+                             args.size() >= 3 ? args[2] : Value(), err);
+}
+
+}  // namespace functions
diff --git a/src/gn/function_filter.cc b/src/gn/function_filter.cc
new file mode 100644 (file)
index 0000000..49eacfa
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kFilterExclude[] = "filter_exclude";
+const char kFilterExclude_HelpShort[] =
+    "filter_exclude: Remove values that match a set of patterns.";
+const char kFilterExclude_Help[] =
+    R"(filter_exclude: Remove values that match a set of patterns.
+
+  filter_exclude(values, exclude_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument exclude_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Any elements in values matching at least one
+  of those patterns will be excluded.
+
+Examples
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_exclude(values, [ "*.proto" ])
+  # result will be [ "foo.cc", "foo.h" ]
+)";
+
+const char kFilterInclude[] = "filter_include";
+const char kFilterInclude_HelpShort[] =
+    "filter_include: Remove values that do not match a set of patterns.";
+const char kFilterInclude_Help[] =
+    R"(filter_include: Remove values that do not match a set of patterns.
+
+  filter_include(values, include_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument include_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Only elements from values matching at least
+  one of the pattern will be included.
+
+Examples
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_include(values, [ "*.proto" ])
+  # result will be [ "foo.proto" ]
+)";
+
+namespace {
+
+enum FilterSelection {
+  kExcludeFilter,
+  kIncludeFilter,
+};
+
+Value RunFilter(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                FilterSelection selection,
+                Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expecting exactly two arguments.");
+    return Value();
+  }
+
+  // Extract "values".
+  if (args[0].type() != Value::LIST) {
+    *err = Err(args[0], "First argument must be a list of strings.");
+    return Value();
+  }
+
+  // Extract "patterns".
+  PatternList patterns;
+  patterns.SetFromValue(args[1], err);
+  if (err->has_error())
+    return Value();
+
+  Value result(function, Value::LIST);
+  for (const auto& value : args[0].list_value()) {
+    if (value.type() != Value::STRING) {
+      *err = Err(args[0], "First argument must be a list of strings.");
+      return Value();
+    }
+
+    const bool matches_pattern = patterns.MatchesValue(value);
+    switch (selection) {
+      case kIncludeFilter:
+        if (matches_pattern)
+          result.list_value().push_back(value);
+        break;
+
+      case kExcludeFilter:
+        if (!matches_pattern)
+          result.list_value().push_back(value);
+        break;
+    }
+  }
+  return result;
+}
+
+}  // anonymous namespace
+
+Value RunFilterExclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err) {
+  return RunFilter(scope, function, args, kExcludeFilter, err);
+}
+
+Value RunFilterInclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err) {
+  return RunFilter(scope, function, args, kIncludeFilter, err);
+}
+
+}  // namespace functions
diff --git a/src/gn/function_filter_unittest.cc b/src/gn/function_filter_unittest.cc
new file mode 100644 (file)
index 0000000..4269f18
--- /dev/null
@@ -0,0 +1,244 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(FilterExcludeTest, Filter) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(result.list_value().size(), 2);
+  EXPECT_EQ(result.list_value()[0].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[0].string_value(), "foo.cc");
+  EXPECT_EQ(result.list_value()[1].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[1].string_value(), "foo.h");
+}
+
+TEST(FilterExcludeTest, NotEnoughArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, TooManyArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  Value extra_argument(nullptr, Value::LIST);
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns, extra_argument};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectValuesType) {
+  TestWithScope setup;
+
+  Value values(nullptr, "foo.cc");
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectValuesElementType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, Value::LIST));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectPatternsType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, "foo.cc");
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, Filter) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(result.list_value().size(), 1);
+  EXPECT_EQ(result.list_value()[0].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[0].string_value(), "foo.proto");
+}
+
+TEST(FilterIncludeTest, NotEnoughArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, TooManyArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  Value extra_argument(nullptr, Value::LIST);
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns, extra_argument};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectValuesType) {
+  TestWithScope setup;
+
+  Value values(nullptr, "foo.cc");
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectValuesElementType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, Value::LIST));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectPatternsType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, "foo.cc");
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
diff --git a/src/gn/function_foreach.cc b/src/gn/function_foreach.cc
new file mode 100644 (file)
index 0000000..9c16e34
--- /dev/null
@@ -0,0 +1,112 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+
+namespace functions {
+
+const char kForEach[] = "foreach";
+const char kForEach_HelpShort[] = "foreach: Iterate over a list.";
+const char kForEach_Help[] =
+    R"(foreach: Iterate over a list.
+
+    foreach(<loop_var>, <list>) {
+      <loop contents>
+    }
+
+  Executes the loop contents block over each item in the list, assigning the
+  loop_var to each item in sequence. The <loop_var> will be a copy so assigning
+  to it will not mutate the list. The loop will iterate over a copy of <list>
+  so mutating it inside the loop will not affect iteration.
+
+  The block does not introduce a new scope, so that variable assignments inside
+  the loop will be visible once the loop terminates.
+
+  The loop variable will temporarily shadow any existing variables with the
+  same name for the duration of the loop. After the loop terminates the loop
+  variable will no longer be in scope, and the previous value (if any) will be
+  restored.
+
+Example
+
+  mylist = [ "a", "b", "c" ]
+  foreach(i, mylist) {
+    print(i)
+  }
+
+  Prints:
+  a
+  b
+  c
+)";
+
+Value RunForEach(Scope* scope,
+                 const FunctionCallNode* function,
+                 const ListNode* args_list,
+                 Err* err) {
+  const auto& args_vector = args_list->contents();
+  if (args_vector.size() != 2) {
+    *err = Err(function, "Wrong number of arguments to foreach().",
+               "Expecting exactly two.");
+    return Value();
+  }
+
+  // Extract the loop variable.
+  const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
+  if (!identifier) {
+    *err =
+        Err(args_vector[0].get(), "Expected an identifier for the loop var.");
+    return Value();
+  }
+  std::string_view loop_var(identifier->value().value());
+
+  // Extract the list to iterate over. Always copy in case the code changes
+  // the list variable inside the loop.
+  Value list_value = args_vector[1]->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  list_value.VerifyTypeIs(Value::Type::LIST, err);
+  if (err->has_error())
+    return Value();
+  const std::vector<Value>& list = list_value.list_value();
+
+  // Block to execute.
+  const BlockNode* block = function->block();
+  if (!block) {
+    *err = Err(function, "Expected { after foreach.");
+    return Value();
+  }
+
+  // If the loop variable was previously defined in this scope, save it so we
+  // can put it back after the loop is done.
+  const Value* old_loop_value_ptr = scope->GetValue(loop_var);
+  Value old_loop_value;
+  if (old_loop_value_ptr)
+    old_loop_value = *old_loop_value_ptr;
+
+  for (const auto& cur : list) {
+    scope->SetValue(loop_var, cur, function);
+    block->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+  }
+
+  // Put back loop var.
+  if (old_loop_value_ptr) {
+    // Put back old value. Use the copy we made, rather than use the pointer,
+    // which will probably point to the new value now in the scope.
+    scope->SetValue(loop_var, std::move(old_loop_value),
+                    old_loop_value.origin());
+  } else {
+    // Loop variable was undefined before loop, delete it.
+    scope->RemoveIdentifier(loop_var);
+  }
+
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_foreach_unittest.cc b/src/gn/function_foreach_unittest.cc
new file mode 100644 (file)
index 0000000..2093d45
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(FunctionForeach, CollisionOnLoopVar) {
+  TestWithScope setup;
+  TestParseInput input(
+      "a = 5\n"
+      "i = 6\n"
+      "foreach(i, [1, 2, 3]) {\n"  // Use same loop var name previously defined.
+      "  print(\"$a $i\")\n"
+      "  a = a + 1\n"  // Test for side effects inside loop.
+      "}\n"
+      "print(\"$a $i\")");  // Make sure that i goes back to original value.
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("5 1\n6 2\n7 3\n8 6\n", setup.print_output());
+}
+
+TEST(FunctionForeach, UniqueLoopVar) {
+  TestWithScope setup;
+  TestParseInput input_good(
+      "foreach(i, [1, 2, 3]) {\n"
+      "  print(i)\n"
+      "}\n");
+  ASSERT_FALSE(input_good.has_error());
+
+  Err err;
+  input_good.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("1\n2\n3\n", setup.print_output());
+  setup.print_output().clear();
+
+  // Same thing but try to use the loop var after loop is done. It should be
+  // undefined and throw an error.
+  TestParseInput input_bad(
+      "foreach(i, [1, 2, 3]) {\n"
+      "  print(i)\n"
+      "}\n"
+      "print(i)");
+  ASSERT_FALSE(input_bad.has_error());  // Should parse OK.
+
+  input_bad.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());  // Shouldn't actually run.
+}
+
+// Checks that the identifier used as the list is marked as "used".
+TEST(FunctionForeach, MarksIdentAsUsed) {
+  TestWithScope setup;
+  TestParseInput input_good(
+      "a = [1, 2]\n"
+      "foreach(i, a) {\n"
+      "  print(i)\n"
+      "}\n");
+  ASSERT_FALSE(input_good.has_error());
+
+  Err err;
+  input_good.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("1\n2\n", setup.print_output());
+  setup.print_output().clear();
+
+  // Check for unused vars.
+  EXPECT_TRUE(setup.scope()->CheckForUnusedVars(&err));
+  EXPECT_FALSE(err.has_error());
+}
+
+// Checks that the list can be modified during iteration without crashing.
+TEST(FunctionForeach, ListModification) {
+  TestWithScope setup;
+  TestParseInput input_grow(
+      "a = [1, 2]\n"
+      "foreach(i, a) {\n"
+      "  print(i)\n"
+      "  if (i <= 8) {\n"
+      "    a += [ i + 2 ]\n"
+      "  }\n"
+      "}\n"
+      "print(a)");
+  ASSERT_FALSE(input_grow.has_error());
+
+  Err err;
+  input_grow.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // The result of the loop should have been unaffected by the mutations of
+  // the list variable inside the loop, but the modifications made to it
+  // should have been persisted.
+  EXPECT_EQ("1\n2\n[1, 2, 3, 4]\n", setup.print_output());
+  setup.print_output().clear();
+}
diff --git a/src/gn/function_forward_variables_from.cc b/src/gn/function_forward_variables_from.cc
new file mode 100644 (file)
index 0000000..ba392e1
--- /dev/null
@@ -0,0 +1,246 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+
+namespace functions {
+
+namespace {
+
+void ForwardAllValues(const FunctionCallNode* function,
+                      Scope* source,
+                      Scope* dest,
+                      const std::set<std::string>& exclusion_set,
+                      Err* err) {
+  Scope::MergeOptions options;
+  // This function needs to clobber existing for it to be useful. It will be
+  // called in a template to forward all values, but there will be some
+  // default stuff like configs set up in both scopes, so it would always
+  // fail if it didn't clobber.
+  options.clobber_existing = true;
+  options.skip_private_vars = true;
+  options.mark_dest_used = false;
+  options.excluded_values = exclusion_set;
+  source->NonRecursiveMergeTo(dest, options, function, "source scope", err);
+  source->MarkAllUsed();
+}
+
+void ForwardValuesFromList(Scope* source,
+                           Scope* dest,
+                           const std::vector<Value>& list,
+                           const std::set<std::string>& exclusion_set,
+                           Err* err) {
+  for (const Value& cur : list) {
+    if (!cur.VerifyTypeIs(Value::STRING, err))
+      return;
+    if (exclusion_set.find(cur.string_value()) != exclusion_set.end())
+      continue;
+    const Value* value = source->GetValue(cur.string_value(), true);
+    if (value) {
+      // Use the storage key for the original value rather than the string in
+      // "cur" because "cur" is a temporary that will be deleted, and Scopes
+      // expect a persistent std::string_view (it won't copy). Not doing this
+      // will lead the scope's key to point to invalid memory after this
+      // returns.
+      std::string_view storage_key = source->GetStorageKey(cur.string_value());
+      if (storage_key.empty()) {
+        // Programmatic value, don't allow copying.
+        *err =
+            Err(cur, "This value can't be forwarded.",
+                "The variable \"" + cur.string_value() + "\" is a built-in.");
+        return;
+      }
+
+      // Don't allow clobbering existing values.
+      const Value* existing_value = dest->GetValue(storage_key);
+      if (existing_value) {
+        *err = Err(
+            cur, "Clobbering existing value.",
+            "The current scope already defines a value \"" +
+                cur.string_value() +
+                "\".\nforward_variables_from() won't clobber "
+                "existing values. If you want to\nmerge lists, you'll need to "
+                "do this explicitly.");
+        err->AppendSubErr(Err(*existing_value, "value being clobbered."));
+        return;
+      }
+
+      // Keep the origin information from the original value. The normal
+      // usage is for this to be used in a template, and if there's an error,
+      // the user expects to see the line where they set the variable
+      // blamed, rather than a template call to forward_variables_from().
+      dest->SetValue(storage_key, *value, value->origin());
+    }
+  }
+}
+
+}  // namespace
+
+const char kForwardVariablesFrom[] = "forward_variables_from";
+const char kForwardVariablesFrom_HelpShort[] =
+    "forward_variables_from: Copies variables from a different scope.";
+const char kForwardVariablesFrom_Help[] =
+    R"(forward_variables_from: Copies variables from a different scope.
+
+  forward_variables_from(from_scope, variable_list_or_star,
+                         variable_to_not_forward_list = [])
+
+  Copies the given variables from the given scope to the local scope if they
+  exist. This is normally used in the context of templates to use the values of
+  variables defined in the template invocation to a template-defined target.
+
+  The variables in the given variable_list will be copied if they exist in the
+  given scope or any enclosing scope. If they do not exist, nothing will happen
+  and they be left undefined in the current scope.
+
+  As a special case, if the variable_list is a string with the value of "*",
+  all variables from the given scope will be copied. "*" only copies variables
+  set directly on the from_scope, not enclosing ones. Otherwise it would
+  duplicate all global variables.
+
+  When an explicit list of variables is supplied, if the variable exists in the
+  current (destination) scope already, an error will be thrown. If "*" is
+  specified, variables in the current scope will be clobbered (the latter is
+  important because most targets have an implicit configs list, which means it
+  wouldn't work at all if it didn't clobber).
+
+  If variables_to_not_forward_list is non-empty, then it must contains a list
+  of variable names that will not be forwarded. This is mostly useful when
+  variable_list_or_star has a value of "*".
+
+Examples
+
+  # forward_variables_from(invoker, ["foo"])
+  # is equivalent to:
+  assert(!defined(foo))
+  if (defined(invoker.foo)) {
+    foo = invoker.foo
+  }
+
+  # This is a common action template. It would invoke a script with some given
+  # parameters, and wants to use the various types of deps and the visibility
+  # from the invoker if it's defined. It also injects an additional dependency
+  # to all targets.
+  template("my_test") {
+    action(target_name) {
+      forward_variables_from(invoker, [ "data_deps", "deps",
+                                        "public_deps", "visibility"])
+      # Add our test code to the dependencies.
+      # "deps" may or may not be defined at this point.
+      if (defined(deps)) {
+        deps += [ "//tools/doom_melon" ]
+      } else {
+        deps = [ "//tools/doom_melon" ]
+      }
+    }
+  }
+
+  # This is a template around a target whose type depends on a global variable.
+  # It forwards all values from the invoker.
+  template("my_wrapper") {
+    target(my_wrapper_target_type, target_name) {
+      forward_variables_from(invoker, "*")
+    }
+  }
+
+  # A template that wraps another. It adds behavior based on one
+  # variable, and forwards all others to the nested target.
+  template("my_ios_test_app") {
+    ios_test_app(target_name) {
+      forward_variables_from(invoker, "*", ["test_bundle_name"])
+      if (!defined(extra_substitutions)) {
+        extra_substitutions = []
+      }
+      extra_substitutions += [ "BUNDLE_ID_TEST_NAME=$test_bundle_name" ]
+    }
+  }
+)";
+
+// This function takes a ListNode rather than a resolved vector of values
+// both avoid copying the potentially-large source scope, and so the variables
+// in the source scope can be marked as used.
+Value RunForwardVariablesFrom(Scope* scope,
+                              const FunctionCallNode* function,
+                              const ListNode* args_list,
+                              Err* err) {
+  const auto& args_vector = args_list->contents();
+  if (args_vector.size() != 2 && args_vector.size() != 3) {
+    *err = Err(function, "Wrong number of arguments.",
+               "Expecting two or three arguments.");
+    return Value();
+  }
+
+  Value* value = nullptr;  // Value to use, may point to result_value.
+  Value result_value;      // Storage for the "evaluate" case.
+  const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
+  if (identifier) {
+    // Optimize the common case where the input scope is an identifier. This
+    // prevents a copy of a potentially large Scope object.
+    value = scope->GetMutableValue(identifier->value().value(),
+                                   Scope::SEARCH_NESTED, true);
+    if (!value) {
+      *err = Err(identifier, "Undefined identifier.");
+      return Value();
+    }
+  } else {
+    // Non-optimized case, just evaluate the argument.
+    result_value = args_vector[0]->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+    value = &result_value;
+  }
+
+  // Extract the source scope.
+  if (!value->VerifyTypeIs(Value::SCOPE, err))
+    return Value();
+  Scope* source = value->scope_value();
+
+  // Extract the exclusion list if defined.
+  std::set<std::string> exclusion_set;
+  if (args_vector.size() == 3) {
+    Value exclusion_value = args_vector[2]->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+
+    if (exclusion_value.type() != Value::LIST) {
+      *err = Err(exclusion_value, "Not a valid list of variables to exclude.",
+                 "Expecting a list of strings.");
+      return Value();
+    }
+
+    for (const Value& cur : exclusion_value.list_value()) {
+      if (!cur.VerifyTypeIs(Value::STRING, err))
+        return Value();
+
+      exclusion_set.insert(cur.string_value());
+    }
+  }
+
+  // Extract the list. If all_values is not set, the what_value will be a list.
+  Value what_value = args_vector[1]->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  if (what_value.type() == Value::STRING) {
+    if (what_value.string_value() == "*") {
+      ForwardAllValues(function, source, scope, exclusion_set, err);
+      return Value();
+    }
+  } else {
+    if (what_value.type() == Value::LIST) {
+      ForwardValuesFromList(source, scope, what_value.list_value(),
+                            exclusion_set, err);
+      return Value();
+    }
+  }
+
+  // Not the right type of argument.
+  *err = Err(what_value, "Not a valid list of variables to copy.",
+             "Expecting either the string \"*\" or a list of strings.");
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_forward_variables_from_unittest.cc b/src/gn/function_forward_variables_from_unittest.cc
new file mode 100644 (file)
index 0000000..2f60f82
--- /dev/null
@@ -0,0 +1,244 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using FunctionForwardVariablesFromTest = TestWithScheduler;
+
+TEST_F(FunctionForwardVariablesFromTest, List) {
+  Err err;
+  std::string program =
+      "template(\"a\") {\n"
+      "  forward_variables_from(invoker, [\"x\", \"y\", \"z\"])\n"
+      "  assert(!defined(z))\n"  // "z" should still be undefined.
+      "  print(\"$target_name, $x, $y\")\n"
+      "}\n"
+      "a(\"target\") {\n"
+      "  x = 1\n"
+      "  y = 2\n"
+      "}\n";
+
+  {
+    TestWithScope setup;
+
+    // Defines a template and copy the two x and y, and z values out.
+    TestParseInput input(program);
+    ASSERT_FALSE(input.has_error());
+
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << err.message();
+
+    EXPECT_EQ("target, 1, 2\n", setup.print_output());
+    setup.print_output().clear();
+  }
+
+  {
+    TestWithScope setup;
+
+    // Test that the same input but forwarding a variable with the name of
+    // something in the given scope throws an error rather than clobbering it.
+    // This uses the same known-good program as before, but adds another
+    // variable in the scope before it.
+    TestParseInput clobber("x = 1\n" + program);
+    ASSERT_FALSE(clobber.has_error());
+
+    clobber.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error());  // Should thow a clobber error.
+    EXPECT_EQ("Clobbering existing value.", err.message());
+  }
+}
+
+TEST_F(FunctionForwardVariablesFromTest, LiteralList) {
+  TestWithScope setup;
+
+  // Forwards all variables from a literal scope into another scope definition.
+  TestParseInput input(
+      "a = {\n"
+      "  forward_variables_from({x = 1 y = 2}, \"*\")\n"
+      "  z = 3\n"
+      "}\n"
+      "print(\"${a.x} ${a.y} ${a.z}\")\n");
+
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("1 2 3\n", setup.print_output());
+  setup.print_output().clear();
+}
+
+TEST_F(FunctionForwardVariablesFromTest, ListWithExclusion) {
+  TestWithScope setup;
+
+  // Defines a template and copy the two x and y, and z values out.
+  TestParseInput input(
+      "template(\"a\") {\n"
+      "  forward_variables_from(invoker, [\"x\", \"y\", \"z\"], [\"z\"])\n"
+      "  assert(!defined(z))\n"  // "z" should still be undefined.
+      "  print(\"$target_name, $x, $y\")\n"
+      "}\n"
+      "a(\"target\") {\n"
+      "  x = 1\n"
+      "  y = 2\n"
+      "  z = 3\n"
+      "  print(\"$z\")\n"
+      "}\n");
+
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("3\ntarget, 1, 2\n", setup.print_output());
+  setup.print_output().clear();
+}
+
+TEST_F(FunctionForwardVariablesFromTest, ErrorCases) {
+  TestWithScope setup;
+
+  // Type check the source scope.
+  TestParseInput invalid_source(
+      "template(\"a\") {\n"
+      "  forward_variables_from(42, [\"x\"])\n"
+      "  print(\"$target_name\")\n"  // Prevent unused var error.
+      "}\n"
+      "a(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(invalid_source.has_error());
+  Err err;
+  invalid_source.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("This is not a scope.", err.message());
+
+  // Type check the list. We need to use a new template name each time since
+  // all of these invocations are executing in sequence in the same scope.
+  TestParseInput invalid_list(
+      "template(\"b\") {\n"
+      "  forward_variables_from(invoker, 42)\n"
+      "  print(\"$target_name\")\n"
+      "}\n"
+      "b(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(invalid_list.has_error());
+  err = Err();
+  invalid_list.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Not a valid list of variables to copy.", err.message());
+
+  // Type check the exclusion list.
+  TestParseInput invalid_exclusion_list(
+      "template(\"c\") {\n"
+      "  forward_variables_from(invoker, \"*\", 42)\n"
+      "  print(\"$target_name\")\n"
+      "}\n"
+      "c(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(invalid_exclusion_list.has_error());
+  err = Err();
+  invalid_exclusion_list.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Not a valid list of variables to exclude.", err.message());
+
+  // Programmatic values should error.
+  TestParseInput prog(
+      "template(\"d\") {\n"
+      "  forward_variables_from(invoker, [\"root_out_dir\"])\n"
+      "  print(\"$target_name\")\n"
+      "}\n"
+      "d(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(prog.has_error());
+  err = Err();
+  prog.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("This value can't be forwarded.", err.message());
+
+  // Not enough arguments.
+  TestParseInput not_enough_arguments(
+      "template(\"e\") {\n"
+      "  forward_variables_from(invoker)\n"
+      "  print(\"$target_name\")\n"
+      "}\n"
+      "e(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(not_enough_arguments.has_error());
+  err = Err();
+  not_enough_arguments.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Wrong number of arguments.", err.message());
+
+  // Too many arguments.
+  TestParseInput too_many_arguments(
+      "template(\"f\") {\n"
+      "  forward_variables_from(invoker, \"*\", [], [])\n"
+      "  print(\"$target_name\")\n"
+      "}\n"
+      "f(\"target\") {\n"
+      "}\n");
+  ASSERT_FALSE(too_many_arguments.has_error());
+  err = Err();
+  too_many_arguments.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Wrong number of arguments.", err.message());
+}
+
+TEST_F(FunctionForwardVariablesFromTest, Star) {
+  TestWithScope setup;
+
+  // Defines a template and copy the two x and y values out. The "*" behavior
+  // should clobber existing variables with the same name.
+  TestParseInput input(
+      "template(\"a\") {\n"
+      "  x = 1000000\n"  // Should be clobbered.
+      "  forward_variables_from(invoker, \"*\")\n"
+      "  print(\"$target_name, $x, $y\")\n"
+      "}\n"
+      "a(\"target\") {\n"
+      "  x = 1\n"
+      "  y = 2\n"
+      "}\n");
+
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("target, 1, 2\n", setup.print_output());
+  setup.print_output().clear();
+}
+
+TEST_F(FunctionForwardVariablesFromTest, StarWithExclusion) {
+  TestWithScope setup;
+
+  // Defines a template and copy all values except z value. The "*" behavior
+  // should clobber existing variables with the same name.
+  TestParseInput input(
+      "template(\"a\") {\n"
+      "  x = 1000000\n"  // Should be clobbered.
+      "  forward_variables_from(invoker, \"*\", [\"z\"])\n"
+      "  print(\"$target_name, $x, $y\")\n"
+      "}\n"
+      "a(\"target\") {\n"
+      "  x = 1\n"
+      "  y = 2\n"
+      "  z = 3\n"
+      "  print(\"$z\")\n"
+      "}\n");
+
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("3\ntarget, 1, 2\n", setup.print_output());
+  setup.print_output().clear();
+}
diff --git a/src/gn/function_get_label_info.cc b/src/gn/function_get_label_info.cc
new file mode 100644 (file)
index 0000000..04fa9a7
--- /dev/null
@@ -0,0 +1,144 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/label.h"
+#include "gn/parse_tree.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kGetLabelInfo[] = "get_label_info";
+const char kGetLabelInfo_HelpShort[] =
+    "get_label_info: Get an attribute from a target's label.";
+const char kGetLabelInfo_Help[] =
+    R"*(get_label_info: Get an attribute from a target's label.
+
+  get_label_info(target_label, what)
+
+  Given the label of a target, returns some attribute of that target. The
+  target need not have been previously defined in the same file, since none of
+  the attributes depend on the actual target definition, only the label itself.
+
+  See also "gn help get_target_outputs".
+
+Possible values for the "what" parameter
+
+  "name"
+      The short name of the target. This will match the value of the
+      "target_name" variable inside that target's declaration. For the label
+      "//foo/bar:baz" this will return "baz".
+
+  "dir"
+      The directory containing the target's definition, with no slash at the
+      end. For the label "//foo/bar:baz" this will return "//foo/bar".
+
+  "target_gen_dir"
+      The generated file directory for the target. This will match the value of
+      the "target_gen_dir" variable when inside that target's declaration.
+
+  "root_gen_dir"
+      The root of the generated file tree for the target. This will match the
+      value of the "root_gen_dir" variable when inside that target's
+      declaration.
+
+  "target_out_dir
+      The output directory for the target. This will match the value of the
+      "target_out_dir" variable when inside that target's declaration.
+
+  "root_out_dir"
+      The root of the output file tree for the target. This will match the
+      value of the "root_out_dir" variable when inside that target's
+      declaration.
+
+  "label_no_toolchain"
+      The fully qualified version of this label, not including the toolchain.
+      For the input ":bar" it might return "//foo:bar".
+
+  "label_with_toolchain"
+      The fully qualified version of this label, including the toolchain. For
+      the input ":bar" it might return "//foo:bar(//toolchain:x64)".
+
+  "toolchain"
+      The label of the toolchain. This will match the value of the
+      "current_toolchain" variable when inside that target's declaration.
+
+Examples
+
+  get_label_info(":foo", "name")
+  # Returns string "foo".
+
+  get_label_info("//foo/bar:baz", "target_gen_dir")
+  # Returns string "//out/Debug/gen/foo/bar".
+)*";
+
+Value RunGetLabelInfo(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expected two arguments.");
+    return Value();
+  }
+
+  // Resolve the requested label.
+  Label label =
+      Label::Resolve(scope->GetSourceDir(),
+                     scope->settings()->build_settings()->root_path_utf8(),
+                     ToolchainLabelForScope(scope), args[0], err);
+  if (label.is_null())
+    return Value();
+
+  // Extract the "what" parameter.
+  if (!args[1].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const std::string& what = args[1].string_value();
+
+  Value result(function, Value::STRING);
+  if (what == "name") {
+    result.string_value() = label.name();
+
+  } else if (what == "dir") {
+    result.string_value() = DirectoryWithNoLastSlash(label.dir());
+
+  } else if (what == "target_gen_dir") {
+    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()), label.dir(),
+        BuildDirType::GEN));
+
+  } else if (what == "root_gen_dir") {
+    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()), BuildDirType::GEN));
+
+  } else if (what == "target_out_dir") {
+    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()), label.dir(),
+        BuildDirType::OBJ));
+
+  } else if (what == "root_out_dir") {
+    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()),
+        BuildDirType::TOOLCHAIN_ROOT));
+
+  } else if (what == "toolchain") {
+    result.string_value() = label.GetToolchainLabel().GetUserVisibleName(false);
+
+  } else if (what == "label_no_toolchain") {
+    result.string_value() =
+        label.GetWithNoToolchain().GetUserVisibleName(false);
+
+  } else if (what == "label_with_toolchain") {
+    result.string_value() = label.GetUserVisibleName(true);
+
+  } else {
+    *err = Err(args[1], "Unknown value for \"what\" parameter.");
+    return Value();
+  }
+
+  return result;
+}
+
+}  // namespace functions
diff --git a/src/gn/function_get_label_info_unittest.cc b/src/gn/function_get_label_info_unittest.cc
new file mode 100644 (file)
index 0000000..3e934f1
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+class GetLabelInfoTest : public testing::Test {
+ public:
+  GetLabelInfoTest() : testing::Test() {
+    setup_.scope()->set_source_dir(SourceDir("//src/foo/"));
+  }
+
+  // Convenience wrapper to call GetLabelInfo.
+  std::string Call(const std::string& label, const std::string& what) {
+    FunctionCallNode function;
+
+    std::vector<Value> args;
+    args.push_back(Value(nullptr, label));
+    args.push_back(Value(nullptr, what));
+
+    Err err;
+    Value result =
+        functions::RunGetLabelInfo(setup_.scope(), &function, args, &err);
+    if (err.has_error()) {
+      EXPECT_TRUE(result.type() == Value::NONE);
+      return std::string();
+    }
+    return result.string_value();
+  }
+
+ protected:
+  // Note: TestWithScope's default toolchain is "//toolchain:default" and
+  // output dir is "//out/Debug".
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(GetLabelInfoTest, BadInput) {
+  EXPECT_EQ("", Call(":name", "incorrect_value"));
+  EXPECT_EQ("", Call("", "name"));
+}
+
+TEST_F(GetLabelInfoTest, Name) {
+  EXPECT_EQ("name", Call(":name", "name"));
+  EXPECT_EQ("name", Call("//foo/bar:name", "name"));
+  EXPECT_EQ("name", Call("//foo/bar:name(//other:tc)", "name"));
+}
+
+TEST_F(GetLabelInfoTest, Dir) {
+  EXPECT_EQ("//src/foo", Call(":name", "dir"));
+  EXPECT_EQ("//foo/bar", Call("//foo/bar:baz", "dir"));
+  EXPECT_EQ("//foo/bar", Call("//foo/bar:baz(//other:tc)", "dir"));
+}
+
+TEST_F(GetLabelInfoTest, RootOutDir) {
+  EXPECT_EQ("//out/Debug", Call(":name", "root_out_dir"));
+  EXPECT_EQ("//out/Debug/random",
+            Call(":name(//toolchain:random)", "root_out_dir"));
+}
+
+TEST_F(GetLabelInfoTest, RootGenDir) {
+  EXPECT_EQ("//out/Debug/gen", Call(":name", "root_gen_dir"));
+  EXPECT_EQ("//out/Debug/gen",
+            Call(":name(//toolchain:default)", "root_gen_dir"));
+  EXPECT_EQ("//out/Debug/random/gen",
+            Call(":name(//toolchain:random)", "root_gen_dir"));
+}
+
+TEST_F(GetLabelInfoTest, TargetOutDir) {
+  EXPECT_EQ("//out/Debug/obj/src/foo", Call(":name", "target_out_dir"));
+  EXPECT_EQ("//out/Debug/obj/foo",
+            Call("//foo:name(//toolchain:default)", "target_out_dir"));
+  EXPECT_EQ("//out/Debug/random/obj/foo",
+            Call("//foo:name(//toolchain:random)", "target_out_dir"));
+}
+
+TEST_F(GetLabelInfoTest, TargetGenDir) {
+  EXPECT_EQ("//out/Debug/gen/src/foo", Call(":name", "target_gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/foo",
+            Call("//foo:name(//toolchain:default)", "target_gen_dir"));
+  EXPECT_EQ("//out/Debug/random/gen/foo",
+            Call("//foo:name(//toolchain:random)", "target_gen_dir"));
+}
+
+TEST_F(GetLabelInfoTest, LabelNoToolchain) {
+  EXPECT_EQ("//src/foo:name", Call(":name", "label_no_toolchain"));
+  EXPECT_EQ("//src/foo:name",
+            Call("//src/foo:name(//toolchain:random)", "label_no_toolchain"));
+}
+
+TEST_F(GetLabelInfoTest, LabelWithToolchain) {
+  EXPECT_EQ("//src/foo:name(//toolchain:default)",
+            Call(":name", "label_with_toolchain"));
+  EXPECT_EQ("//src/foo:name(//toolchain:random)",
+            Call(":name(//toolchain:random)", "label_with_toolchain"));
+}
+
+TEST_F(GetLabelInfoTest, Toolchain) {
+  EXPECT_EQ("//toolchain:default", Call(":name", "toolchain"));
+  EXPECT_EQ("//toolchain:random",
+            Call(":name(//toolchain:random)", "toolchain"));
+}
diff --git a/src/gn/function_get_path_info.cc b/src/gn/function_get_path_info.cc
new file mode 100644 (file)
index 0000000..00ed043
--- /dev/null
@@ -0,0 +1,251 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+
+namespace functions {
+
+namespace {
+
+// Corresponds to the various values of "what" in the function call.
+enum What {
+  WHAT_FILE,
+  WHAT_NAME,
+  WHAT_EXTENSION,
+  WHAT_DIR,
+  WHAT_ABSPATH,
+  WHAT_GEN_DIR,
+  WHAT_OUT_DIR,
+};
+
+// Returns the directory containing the input (resolving it against the
+// |current_dir|), regardless of whether the input is a directory or a file.
+SourceDir DirForInput(const Settings* settings,
+                      const SourceDir& current_dir,
+                      const Value& input,
+                      Err* err) {
+  // Input should already have been validated as a string.
+  const std::string& input_string = input.string_value();
+
+  if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
+    // Input is a directory.
+    return current_dir.ResolveRelativeDir(
+        input, err, settings->build_settings()->root_path_utf8());
+  }
+
+  // Input is a file.
+  return current_dir
+      .ResolveRelativeFile(input, err,
+                           settings->build_settings()->root_path_utf8())
+      .GetDir();
+}
+
+std::string GetOnePathInfo(const Settings* settings,
+                           const SourceDir& current_dir,
+                           What what,
+                           const Value& input,
+                           Err* err) {
+  if (!input.VerifyTypeIs(Value::STRING, err))
+    return std::string();
+  const std::string& input_string = input.string_value();
+  if (input_string.empty()) {
+    *err = Err(input, "Calling get_path_info on an empty string.");
+    return std::string();
+  }
+
+  switch (what) {
+    case WHAT_FILE: {
+      return std::string(FindFilename(&input_string));
+    }
+    case WHAT_NAME: {
+      std::string file(FindFilename(&input_string));
+      size_t extension_offset = FindExtensionOffset(file);
+      if (extension_offset == std::string::npos)
+        return file;
+      // Trim extension and dot.
+      return file.substr(0, extension_offset - 1);
+    }
+    case WHAT_EXTENSION: {
+      return std::string(FindExtension(&input_string));
+    }
+    case WHAT_DIR: {
+      std::string_view dir_incl_slash = FindDir(&input_string);
+      if (dir_incl_slash.empty())
+        return std::string(".");
+      // Trim slash since this function doesn't return trailing slashes. The
+      // times we don't do this are if the result is "/" and "//" since those
+      // slashes can't be trimmed.
+      if (dir_incl_slash == "/")
+        return std::string("/.");
+      if (dir_incl_slash == "//")
+        return std::string("//.");
+      return std::string(dir_incl_slash.substr(0, dir_incl_slash.size() - 1));
+    }
+    case WHAT_GEN_DIR: {
+      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings),
+          DirForInput(settings, current_dir, input, err), BuildDirType::GEN));
+    }
+    case WHAT_OUT_DIR: {
+      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings),
+          DirForInput(settings, current_dir, input, err), BuildDirType::OBJ));
+    }
+    case WHAT_ABSPATH: {
+      bool as_dir =
+          !input_string.empty() && input_string[input_string.size() - 1] == '/';
+
+      return current_dir.ResolveRelativeAs(
+          !as_dir, input, err, settings->build_settings()->root_path_utf8(),
+          &input_string);
+    }
+    default:
+      NOTREACHED();
+      return std::string();
+  }
+}
+
+}  // namespace
+
+const char kGetPathInfo[] = "get_path_info";
+const char kGetPathInfo_HelpShort[] =
+    "get_path_info: Extract parts of a file or directory name.";
+const char kGetPathInfo_Help[] =
+    R"(get_path_info: Extract parts of a file or directory name.
+
+  get_path_info(input, what)
+
+  The first argument is either a string representing a file or directory name,
+  or a list of such strings. If the input is a list the return value will be a
+  list containing the result of applying the rule to each item in the input.
+
+Possible values for the "what" parameter
+
+  "file"
+      The substring after the last slash in the path, including the name and
+      extension. If the input ends in a slash, the empty string will be
+      returned.
+        "foo/bar.txt" => "bar.txt"
+        "bar.txt" => "bar.txt"
+        "foo/" => ""
+        "" => ""
+
+  "name"
+     The substring of the file name not including the extension.
+        "foo/bar.txt" => "bar"
+        "foo/bar" => "bar"
+        "foo/" => ""
+
+  "extension"
+      The substring following the last period following the last slash, or the
+      empty string if not found. The period is not included.
+        "foo/bar.txt" => "txt"
+        "foo/bar" => ""
+
+  "dir"
+      The directory portion of the name, not including the slash.
+        "foo/bar.txt" => "foo"
+        "//foo/bar" => "//foo"
+        "foo" => "."
+
+      The result will never end in a slash, so if the resulting is empty, the
+      system ("/") or source ("//") roots, a "." will be appended such that it
+      is always legal to append a slash and a filename and get a valid path.
+
+  "out_dir"
+      The output file directory corresponding to the path of the given file,
+      not including a trailing slash.
+        "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar"
+
+  "gen_dir"
+      The generated file directory corresponding to the path of the given file,
+      not including a trailing slash.
+        "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar"
+
+  "abspath"
+      The full absolute path name to the file or directory. It will be resolved
+      relative to the current directory, and then the source- absolute version
+      will be returned. If the input is system- absolute, the same input will
+      be returned.
+        "foo/bar.txt" => "//mydir/foo/bar.txt"
+        "foo/" => "//mydir/foo/"
+        "//foo/bar" => "//foo/bar"  (already absolute)
+        "/usr/include" => "/usr/include"  (already absolute)
+
+      If you want to make the path relative to another directory, or to be
+      system-absolute, see rebase_path().
+
+Examples
+  sources = [ "foo.cc", "foo.h" ]
+  result = get_path_info(source, "abspath")
+  # result will be [ "//mydir/foo.cc", "//mydir/foo.h" ]
+
+  result = get_path_info("//foo/bar/baz.cc", "dir")
+  # result will be "//foo/bar"
+
+  # Extract the source-absolute directory name,
+  result = get_path_info(get_path_info(path, "dir"), "abspath")
+)";
+
+Value RunGetPathInfo(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expecting two arguments to get_path_info.");
+    return Value();
+  }
+
+  // Extract the "what".
+  if (!args[1].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  What what;
+  if (args[1].string_value() == "file") {
+    what = WHAT_FILE;
+  } else if (args[1].string_value() == "name") {
+    what = WHAT_NAME;
+  } else if (args[1].string_value() == "extension") {
+    what = WHAT_EXTENSION;
+  } else if (args[1].string_value() == "dir") {
+    what = WHAT_DIR;
+  } else if (args[1].string_value() == "out_dir") {
+    what = WHAT_OUT_DIR;
+  } else if (args[1].string_value() == "gen_dir") {
+    what = WHAT_GEN_DIR;
+  } else if (args[1].string_value() == "abspath") {
+    what = WHAT_ABSPATH;
+  } else {
+    *err = Err(args[1], "Unknown value for 'what'.");
+    return Value();
+  }
+
+  const SourceDir& current_dir = scope->GetSourceDir();
+  if (args[0].type() == Value::STRING) {
+    return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
+                                          args[0], err));
+  } else if (args[0].type() == Value::LIST) {
+    const std::vector<Value>& input_list = args[0].list_value();
+    Value result(function, Value::LIST);
+    for (const auto& cur : input_list) {
+      result.list_value().push_back(Value(
+          function,
+          GetOnePathInfo(scope->settings(), current_dir, what, cur, err)));
+      if (err->has_error())
+        return Value();
+    }
+    return result;
+  }
+
+  *err = Err(args[0], "Path must be a string or a list of strings.");
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_get_path_info_unittest.cc b/src/gn/function_get_path_info_unittest.cc
new file mode 100644 (file)
index 0000000..4afad0c
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+class GetPathInfoTest : public testing::Test {
+ public:
+  GetPathInfoTest() : testing::Test() {
+    setup_.scope()->set_source_dir(SourceDir("//src/foo/"));
+  }
+
+  // Convenience wrapper to call GetLabelInfo.
+  std::string Call(const std::string& input, const std::string& what) {
+    FunctionCallNode function;
+
+    std::vector<Value> args;
+    args.push_back(Value(nullptr, input));
+    args.push_back(Value(nullptr, what));
+
+    Err err;
+    Value result =
+        functions::RunGetPathInfo(setup_.scope(), &function, args, &err);
+    if (err.has_error()) {
+      EXPECT_TRUE(result.type() == Value::NONE);
+      return std::string();
+    }
+    return result.string_value();
+  }
+
+ protected:
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(GetPathInfoTest, File) {
+  EXPECT_EQ("bar.txt", Call("foo/bar.txt", "file"));
+  EXPECT_EQ("bar.txt", Call("bar.txt", "file"));
+  EXPECT_EQ("bar.txt", Call("/bar.txt", "file"));
+  EXPECT_EQ("", Call("foo/", "file"));
+  EXPECT_EQ("", Call("//", "file"));
+  EXPECT_EQ("", Call("/", "file"));
+}
+
+TEST_F(GetPathInfoTest, Name) {
+  EXPECT_EQ("bar", Call("foo/bar.txt", "name"));
+  EXPECT_EQ("bar", Call("bar.", "name"));
+  EXPECT_EQ("", Call("/.txt", "name"));
+  EXPECT_EQ("", Call("foo/", "name"));
+  EXPECT_EQ("", Call("//", "name"));
+  EXPECT_EQ("", Call("/", "name"));
+}
+
+TEST_F(GetPathInfoTest, Extension) {
+  EXPECT_EQ("txt", Call("foo/bar.txt", "extension"));
+  EXPECT_EQ("", Call("bar.", "extension"));
+  EXPECT_EQ("txt", Call("/.txt", "extension"));
+  EXPECT_EQ("", Call("f.oo/", "extension"));
+  EXPECT_EQ("", Call("//", "extension"));
+  EXPECT_EQ("", Call("/", "extension"));
+}
+
+TEST_F(GetPathInfoTest, Dir) {
+  EXPECT_EQ("foo", Call("foo/bar.txt", "dir"));
+  EXPECT_EQ(".", Call("bar.txt", "dir"));
+  EXPECT_EQ("foo/bar", Call("foo/bar/baz", "dir"));
+  EXPECT_EQ("//foo", Call("//foo/", "dir"));
+  EXPECT_EQ("//.", Call("//", "dir"));
+  EXPECT_EQ("/foo", Call("/foo/", "dir"));
+  EXPECT_EQ("/.", Call("/", "dir"));
+}
+
+// Note "current dir" is "//src/foo"
+TEST_F(GetPathInfoTest, AbsPath) {
+  EXPECT_EQ("//src/foo/foo/bar.txt", Call("foo/bar.txt", "abspath"));
+  EXPECT_EQ("//src/foo/bar.txt", Call("bar.txt", "abspath"));
+  EXPECT_EQ("//src/foo/bar/", Call("bar/", "abspath"));
+  EXPECT_EQ("//foo", Call("//foo", "abspath"));
+  EXPECT_EQ("//foo/", Call("//foo/", "abspath"));
+  EXPECT_EQ("//", Call("//", "abspath"));
+  EXPECT_EQ("/foo", Call("/foo", "abspath"));
+  EXPECT_EQ("/foo/", Call("/foo/", "abspath"));
+  EXPECT_EQ("/", Call("/", "abspath"));
+}
+
+// Note build dir is "//out/Debug/".
+TEST_F(GetPathInfoTest, OutDir) {
+  EXPECT_EQ("//out/Debug/obj/src/foo/foo", Call("foo/bar.txt", "out_dir"));
+  EXPECT_EQ("//out/Debug/obj/src/foo/bar", Call("bar/", "out_dir"));
+  EXPECT_EQ("//out/Debug/obj/src/foo", Call(".", "out_dir"));
+  EXPECT_EQ("//out/Debug/obj/src/foo", Call("bar", "out_dir"));
+  EXPECT_EQ("//out/Debug/obj/foo", Call("//foo/bar.txt", "out_dir"));
+  // System paths go into the ABS_PATH obj directory.
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/foo", Call("/foo/bar.txt", "out_dir"));
+#if defined(OS_WIN)
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/foo",
+            Call("/C:/foo/bar.txt", "out_dir"));
+#endif
+}
+
+// Note build dir is "//out/Debug/".
+TEST_F(GetPathInfoTest, GenDir) {
+  EXPECT_EQ("//out/Debug/gen/src/foo/foo", Call("foo/bar.txt", "gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/src/foo/bar", Call("bar/", "gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/src/foo", Call(".", "gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/src/foo", Call("bar", "gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/foo", Call("//foo/bar.txt", "gen_dir"));
+  // System paths go into the ABS_PATH gen directory
+  EXPECT_EQ("//out/Debug/gen/ABS_PATH/foo", Call("/foo/bar.txt", "gen_dir"));
+#if defined(OS_WIN)
+  EXPECT_EQ("//out/Debug/gen/ABS_PATH/C/foo",
+            Call("/C:/foo/bar.txt", "gen_dir"));
+#endif
+}
diff --git a/src/gn/function_get_target_outputs.cc b/src/gn/function_get_target_outputs.cc
new file mode 100644 (file)
index 0000000..fa851f3
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/build_settings.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/settings.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kGetTargetOutputs[] = "get_target_outputs";
+const char kGetTargetOutputs_HelpShort[] =
+    "get_target_outputs: [file list] Get the list of outputs from a target.";
+const char kGetTargetOutputs_Help[] =
+    R"(get_target_outputs: [file list] Get the list of outputs from a target.
+
+  get_target_outputs(target_label)
+
+  Returns a list of output files for the named target. The named target must
+  have been previously defined in the current file before this function is
+  called (it can't reference targets in other files because there isn't a
+  defined execution order, and it obviously can't reference targets that are
+  defined after the function call).
+
+  Only copy, generated_file, and action targets are supported. The outputs from
+  binary targets will depend on the toolchain definition which won't
+  necessarily have been loaded by the time a given line of code has run, and
+  source sets and groups have no useful output file.
+
+Return value
+
+  The names in the resulting list will be absolute file paths (normally like
+  "//out/Debug/bar.exe", depending on the build directory).
+
+  action, copy, and generated_file targets: this will just return the files
+  specified in the "outputs" variable of the target.
+
+  action_foreach targets: this will return the result of applying the output
+  template to the sources (see "gn help source_expansion"). This will be the
+  same result (though with guaranteed absolute file paths), as
+  process_file_template will return for those inputs (see "gn help
+  process_file_template").
+
+  source sets and groups: this will return a list containing the path of the
+  "stamp" file that Ninja will produce once all outputs are generated. This
+  probably isn't very useful.
+
+Example
+
+  # Say this action generates a bunch of C source files.
+  action_foreach("my_action") {
+    sources = [ ... ]
+    outputs = [ ... ]
+  }
+
+  # Compile the resulting source files into a source set.
+  source_set("my_lib") {
+    sources = get_target_outputs(":my_action")
+  }
+)";
+
+Value RunGetTargetOutputs(Scope* scope,
+                          const FunctionCallNode* function,
+                          const std::vector<Value>& args,
+                          Err* err) {
+  if (args.size() != 1) {
+    *err = Err(function, "Expected one argument.");
+    return Value();
+  }
+
+  // Resolve the requested label.
+  Label label =
+      Label::Resolve(scope->GetSourceDir(),
+                     scope->settings()->build_settings()->root_path_utf8(),
+                     ToolchainLabelForScope(scope), args[0], err);
+  if (label.is_null())
+    return Value();
+
+  // Find the referenced target. The targets previously encountered in this
+  // scope will have been stashed in the item collector (they'll be dispatched
+  // when this file is done running) so we can look through them.
+  const Target* target = nullptr;
+  Scope::ItemVector* collector = scope->GetItemCollector();
+  if (!collector) {
+    *err = Err(function, "No targets defined in this context.");
+    return Value();
+  }
+  for (const auto& item : *collector) {
+    if (item->label() != label)
+      continue;
+
+    const Target* as_target = item->AsTarget();
+    if (!as_target) {
+      *err = Err(function, "Label does not refer to a target.",
+                 label.GetUserVisibleName(false) + "\nrefers to a " +
+                     item->GetItemTypeName());
+      return Value();
+    }
+    target = as_target;
+    break;
+  }
+
+  if (!target) {
+    *err = Err(function, "Target not found in this context.",
+               label.GetUserVisibleName(false) +
+                   "\nwas not found. get_target_outputs() can only be used for "
+                   "targets\n"
+                   "previously defined in the current file.");
+    return Value();
+  }
+
+  // Range for GetOutputsAsSourceFiles to blame for errors.
+  LocationRange arg_range;
+  if (args[0].origin())
+    arg_range = args[0].origin()->GetRange();
+
+  std::vector<SourceFile> files;
+
+  // The build is currently running so only non-binary targets (they don't
+  // depend on the toolchain definition which may not have been loaded yet) can
+  // be queried. Pass false for build_complete so it will flag such queries as
+  // an error.
+  if (!target->GetOutputsAsSourceFiles(arg_range, false, &files, err))
+    return Value();
+
+  // Convert to Values.
+  Value ret(function, Value::LIST);
+  ret.list_value().reserve(files.size());
+  for (const auto& file : files)
+    ret.list_value().push_back(Value(function, file.value()));
+
+  return ret;
+}
+
+}  // namespace functions
diff --git a/src/gn/function_get_target_outputs_unittest.cc b/src/gn/function_get_target_outputs_unittest.cc
new file mode 100644 (file)
index 0000000..4cbd8f9
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "gn/functions.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+class GetTargetOutputsTest : public testing::Test {
+ public:
+  GetTargetOutputsTest() { setup_.scope()->set_item_collector(&items_); }
+
+  Value GetTargetOutputs(const std::string& name, Err* err) {
+    FunctionCallNode function;
+    std::vector<Value> args;
+    args.push_back(Value(nullptr, name));
+    return functions::RunGetTargetOutputs(setup_.scope(), &function, args, err);
+  }
+
+  // Shortcut to get a label with the current toolchain.
+  Label GetLabel(const std::string& dir, const std::string& name) {
+    return Label(SourceDir(dir), name, setup_.toolchain()->label().dir(),
+                 setup_.toolchain()->label().name());
+  }
+
+  // Asserts that the given list contains a single string with the given value.
+  void AssertSingleStringEquals(const Value& list,
+                                const std::string& expected) {
+    ASSERT_TRUE(list.type() == Value::LIST);
+    ASSERT_EQ(1u, list.list_value().size());
+    ASSERT_TRUE(list.list_value()[0].type() == Value::STRING);
+    ASSERT_EQ(expected, list.list_value()[0].string_value());
+  }
+
+  void AssertTwoStringsEqual(const Value& list,
+                             const std::string& expected1,
+                             const std::string& expected2) {
+    ASSERT_TRUE(list.type() == Value::LIST);
+    ASSERT_EQ(2u, list.list_value().size());
+    ASSERT_TRUE(list.list_value()[0].type() == Value::STRING);
+    ASSERT_EQ(expected1, list.list_value()[0].string_value());
+    ASSERT_TRUE(list.list_value()[1].type() == Value::STRING);
+    ASSERT_EQ(expected2, list.list_value()[1].string_value());
+  }
+
+ protected:
+  TestWithScope setup_;
+
+  Scope::ItemVector items_;
+};
+
+}  // namespace
+
+TEST_F(GetTargetOutputsTest, Copy) {
+  auto action =
+      std::make_unique<Target>(setup_.settings(), GetLabel("//foo/", "bar"));
+  action->set_output_type(Target::COPY_FILES);
+  action->sources().push_back(SourceFile("//file.txt"));
+  action->action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_file_part}}.one");
+
+  items_.push_back(std::move(action));
+
+  Err err;
+  Value result = GetTargetOutputs("//foo:bar", &err);
+  ASSERT_FALSE(err.has_error());
+  AssertSingleStringEquals(result, "//out/Debug/file.txt.one");
+}
+
+TEST_F(GetTargetOutputsTest, Action) {
+  auto action =
+      std::make_unique<Target>(setup_.settings(), GetLabel("//foo/", "bar"));
+  action->set_output_type(Target::ACTION);
+  action->action_values().outputs() =
+      SubstitutionList::MakeForTest("//output1.txt", "//output2.txt");
+
+  items_.push_back(std::move(action));
+
+  Err err;
+  Value result = GetTargetOutputs("//foo:bar", &err);
+  ASSERT_FALSE(err.has_error());
+  AssertTwoStringsEqual(result, "//output1.txt", "//output2.txt");
+}
+
+TEST_F(GetTargetOutputsTest, ActionForeach) {
+  auto action =
+      std::make_unique<Target>(setup_.settings(), GetLabel("//foo/", "bar"));
+  action->set_output_type(Target::ACTION_FOREACH);
+  action->sources().push_back(SourceFile("//file.txt"));
+  action->action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_file_part}}.one",
+                                    "//out/Debug/{{source_file_part}}.two");
+
+  items_.push_back(std::move(action));
+
+  Err err;
+  Value result = GetTargetOutputs("//foo:bar", &err);
+  ASSERT_FALSE(err.has_error());
+  AssertTwoStringsEqual(result, "//out/Debug/file.txt.one",
+                        "//out/Debug/file.txt.two");
+}
diff --git a/src/gn/function_process_file_template.cc b/src/gn/function_process_file_template.cc
new file mode 100644 (file)
index 0000000..d4e1e56
--- /dev/null
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/value_extractors.h"
+
+namespace functions {
+
+const char kProcessFileTemplate[] = "process_file_template";
+const char kProcessFileTemplate_HelpShort[] =
+    "process_file_template: Do template expansion over a list of files.";
+const char kProcessFileTemplate_Help[] =
+    R"(process_file_template: Do template expansion over a list of files.
+
+  process_file_template(source_list, template)
+
+  process_file_template applies a template list to a source file list,
+  returning the result of applying each template to each source. This is
+  typically used for computing output file names from input files.
+
+  In most cases, get_target_outputs() will give the same result with shorter,
+  more maintainable code. This function should only be used when that function
+  can't be used (like there's no target or the target is defined in another
+  build file).
+
+Arguments
+
+  The source_list is a list of file names.
+
+  The template can be a string or a list. If it is a list, multiple output
+  strings are generated for each input.
+
+  The template should contain source expansions to which each name in the
+  source list is applied. See "gn help source_expansion".
+
+Example
+
+  sources = [
+    "foo.idl",
+    "bar.idl",
+  ]
+  myoutputs = process_file_template(
+      sources,
+      [ "$target_gen_dir/{{source_name_part}}.cc",
+        "$target_gen_dir/{{source_name_part}}.h" ])
+
+ The result in this case will be:
+    [ "//out/Debug/foo.cc"
+      "//out/Debug/foo.h"
+      "//out/Debug/bar.cc"
+      "//out/Debug/bar.h" ]
+)";
+
+Value RunProcessFileTemplate(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function->function(), "Expected two arguments");
+    return Value();
+  }
+
+  // Source list.
+  Target::FileList input_files;
+  if (!ExtractListOfRelativeFiles(scope->settings()->build_settings(), args[0],
+                                  scope->GetSourceDir(), &input_files, err))
+    return Value();
+
+  std::vector<std::string> result_files;
+  SubstitutionList subst;
+
+  // Template.
+  const Value& template_arg = args[1];
+  if (template_arg.type() == Value::STRING) {
+    // Convert the string to a SubstitutionList with one pattern in it to
+    // simplify the code below.
+    std::vector<std::string> list;
+    list.push_back(template_arg.string_value());
+    if (!subst.Parse(list, template_arg.origin(), err))
+      return Value();
+  } else if (template_arg.type() == Value::LIST) {
+    if (!subst.Parse(template_arg, err))
+      return Value();
+  } else {
+    *err = Err(template_arg, "Not a string or a list.");
+    return Value();
+  }
+
+  auto& types = subst.required_types();
+  if (base::ContainsValue(types, &SubstitutionSourceTargetRelative)) {
+    *err = Err(template_arg, "Not a valid substitution type for the function.");
+    return Value();
+  }
+
+  SubstitutionWriter::ApplyListToSourcesAsString(
+      nullptr, scope->settings(), subst, input_files, &result_files);
+
+  // Convert the list of strings to the return Value.
+  Value ret(function, Value::LIST);
+  ret.list_value().reserve(result_files.size());
+  for (const auto& file : result_files)
+    ret.list_value().push_back(Value(function, file));
+
+  return ret;
+}
+
+}  // namespace functions
diff --git a/src/gn/function_process_file_template_unittest.cc b/src/gn/function_process_file_template_unittest.cc
new file mode 100644 (file)
index 0000000..d9c9919
--- /dev/null
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(FunctionProcessFileTemplates, SingleString) {
+  TestWithScope setup;
+
+  std::vector<Value> args;
+
+  Value sources(nullptr, Value::LIST);
+  sources.list_value().push_back(Value(nullptr, "//src/foo.txt"));
+  args.push_back(sources);
+
+  Value expansion(nullptr, "1234{{source_name_part}}5678");
+  args.push_back(expansion);
+
+  Err err;
+  Value result =
+      functions::RunProcessFileTemplate(setup.scope(), nullptr, args, &err);
+  EXPECT_FALSE(err.has_error());
+
+  ASSERT_TRUE(result.type() == Value::LIST);
+  ASSERT_EQ(1u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("1234foo5678", result.list_value()[0].string_value());
+}
+
+TEST(FunctionProcessFileTemplates, MultipleStrings) {
+  TestWithScope setup;
+
+  std::vector<Value> args;
+
+  Value sources(nullptr, Value::LIST);
+  sources.list_value().push_back(Value(nullptr, "//src/one.txt"));
+  sources.list_value().push_back(Value(nullptr, "//src/two.txt"));
+  args.push_back(sources);
+
+  Value expansions(nullptr, Value::LIST);
+  expansions.list_value().push_back(
+      Value(nullptr, "1234{{source_name_part}}5678"));
+  expansions.list_value().push_back(
+      Value(nullptr, "ABCD{{source_file_part}}EFGH"));
+  args.push_back(expansions);
+
+  Err err;
+  Value result =
+      functions::RunProcessFileTemplate(setup.scope(), nullptr, args, &err);
+  EXPECT_FALSE(err.has_error());
+
+  ASSERT_TRUE(result.type() == Value::LIST);
+  ASSERT_EQ(4u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_TRUE(result.list_value()[1].type() == Value::STRING);
+  ASSERT_TRUE(result.list_value()[2].type() == Value::STRING);
+  ASSERT_TRUE(result.list_value()[3].type() == Value::STRING);
+  ASSERT_EQ("1234one5678", result.list_value()[0].string_value());
+  ASSERT_EQ("ABCDone.txtEFGH", result.list_value()[1].string_value());
+  ASSERT_EQ("1234two5678", result.list_value()[2].string_value());
+  ASSERT_EQ("ABCDtwo.txtEFGH", result.list_value()[3].string_value());
+}
diff --git a/src/gn/function_read_file.cc b/src/gn/function_read_file.cc
new file mode 100644 (file)
index 0000000..7aedf8e
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/input_conversion.h"
+#include "gn/input_file.h"
+#include "gn/scheduler.h"
+
+// TODO(brettw) consider removing this. I originally wrote it for making the
+// WebKit bindings but misundersood what was required, and didn't need to
+// use this. This seems to have a high potential for misuse.
+
+namespace functions {
+
+const char kReadFile[] = "read_file";
+const char kReadFile_HelpShort[] = "read_file: Read a file into a variable.";
+const char kReadFile_Help[] =
+    R"(read_file: Read a file into a variable.
+
+  read_file(filename, input_conversion)
+
+  Whitespace will be trimmed from the end of the file. Throws an error if the
+  file can not be opened.
+
+Arguments
+
+  filename
+      Filename to read, relative to the build file.
+
+  input_conversion
+      Controls how the file is read and parsed. See "gn help io_conversion".
+
+Example
+
+  lines = read_file("foo.txt", "list lines")
+)";
+
+Value RunReadFile(Scope* scope,
+                  const FunctionCallNode* function,
+                  const std::vector<Value>& args,
+                  Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function->function(), "Wrong number of arguments to read_file",
+               "I expected two arguments.");
+    return Value();
+  }
+  if (!args[0].VerifyTypeIs(Value::STRING, err))
+    return Value();
+
+  // Compute the file name.
+  const SourceDir& cur_dir = scope->GetSourceDir();
+  SourceFile source_file = cur_dir.ResolveRelativeFile(
+      args[0], err, scope->settings()->build_settings()->root_path_utf8());
+  if (err->has_error())
+    return Value();
+  base::FilePath file_path =
+      scope->settings()->build_settings()->GetFullPath(source_file);
+
+  // Ensure that everything is recomputed if the read file changes.
+  g_scheduler->AddGenDependency(file_path);
+
+  // Read contents.
+  std::string file_contents;
+  if (!base::ReadFileToString(file_path, &file_contents)) {
+    *err = Err(args[0], "Could not read file.",
+               "I resolved this to \"" + FilePathToUTF8(file_path) + "\".");
+    return Value();
+  }
+
+  return ConvertInputToValue(scope->settings(), file_contents, function,
+                             args[1], err);
+}
+
+}  // namespace functions
diff --git a/src/gn/function_rebase_path.cc b/src/gn/function_rebase_path.cc
new file mode 100644 (file)
index 0000000..7e912aa
--- /dev/null
@@ -0,0 +1,288 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/build_settings.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/value.h"
+
+namespace functions {
+
+namespace {
+
+// We want the output to match the input in terms of ending in a slash or not.
+// Through all the transformations, these can get added or removed in various
+// cases.
+void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
+  if (EndsWithSlash(input)) {
+    if (!EndsWithSlash(*output))  // Preserve same slash type as input.
+      output->push_back(input[input.size() - 1]);
+  } else {
+    if (EndsWithSlash(*output))
+      output->resize(output->size() - 1);
+  }
+}
+
+// Returns true if the given value looks like a directory, otherwise we'll
+// assume it's a file.
+bool ValueLooksLikeDir(const std::string& value) {
+  if (value.empty())
+    return true;
+  size_t value_size = value.size();
+
+  // Count the number of dots at the end of the string.
+  size_t num_dots = 0;
+  while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
+    num_dots++;
+
+  if (num_dots == value.size())
+    return true;  // String is all dots.
+
+  if (IsSlash(value[value_size - num_dots - 1]))
+    return true;  // String is a [back]slash followed by 0 or more dots.
+
+  // Anything else.
+  return false;
+}
+
+Value ConvertOnePath(const Scope* scope,
+                     const FunctionCallNode* function,
+                     const Value& value,
+                     const SourceDir& from_dir,
+                     const SourceDir& to_dir,
+                     bool convert_to_system_absolute,
+                     Err* err) {
+  Value result;  // Ensure return value optimization.
+
+  if (!value.VerifyTypeIs(Value::STRING, err))
+    return result;
+  const std::string& string_value = value.string_value();
+
+  bool looks_like_dir = ValueLooksLikeDir(string_value);
+
+  // System-absolute output special case.
+  if (convert_to_system_absolute) {
+    base::FilePath system_path;
+    if (looks_like_dir) {
+      system_path = scope->settings()->build_settings()->GetFullPath(
+          from_dir.ResolveRelativeDir(
+              value, err,
+              scope->settings()->build_settings()->root_path_utf8()));
+    } else {
+      system_path = scope->settings()->build_settings()->GetFullPath(
+          from_dir.ResolveRelativeFile(
+              value, err,
+              scope->settings()->build_settings()->root_path_utf8()));
+    }
+    if (err->has_error())
+      return Value();
+
+    result = Value(function, FilePathToUTF8(system_path));
+    if (looks_like_dir)
+      MakeSlashEndingMatchInput(string_value, &result.string_value());
+    return result;
+  }
+
+  result = Value(function, Value::STRING);
+  if (looks_like_dir) {
+    result.string_value() = RebasePath(
+        from_dir
+            .ResolveRelativeDir(
+                value, err,
+                scope->settings()->build_settings()->root_path_utf8())
+            .value(),
+        to_dir, scope->settings()->build_settings()->root_path_utf8());
+    MakeSlashEndingMatchInput(string_value, &result.string_value());
+  } else {
+    SourceFile resolved_file = from_dir.ResolveRelativeFile(
+        value, err, scope->settings()->build_settings()->root_path_utf8());
+    if (err->has_error())
+      return Value();
+    result.string_value() =
+        RebasePath(resolved_file.value(), to_dir,
+                   scope->settings()->build_settings()->root_path_utf8());
+  }
+
+  return result;
+}
+
+}  // namespace
+
+const char kRebasePath[] = "rebase_path";
+const char kRebasePath_HelpShort[] =
+    "rebase_path: Rebase a file or directory to another location.";
+const char kRebasePath_Help[] =
+    R"(rebase_path: Rebase a file or directory to another location.
+
+  converted = rebase_path(input,
+                          new_base = "",
+                          current_base = ".")
+
+  Takes a string argument representing a file name, or a list of such strings
+  and converts it/them to be relative to a different base directory.
+
+  When invoking the compiler or scripts, GN will automatically convert sources
+  and include directories to be relative to the build directory. However, if
+  you're passing files directly in the "args" array or doing other manual
+  manipulations where GN doesn't know something is a file name, you will need
+  to convert paths to be relative to what your tool is expecting.
+
+  The common case is to use this to convert paths relative to the current
+  directory to be relative to the build directory (which will be the current
+  directory when executing scripts).
+
+  If you want to convert a file path to be source-absolute (that is, beginning
+  with a double slash like "//foo/bar"), you should use the get_path_info()
+  function. This function won't work because it will always make relative
+  paths, and it needs to support making paths relative to the source root, so
+  it can't also generate source-absolute paths without more special-cases.
+
+Arguments
+
+  input
+      A string or list of strings representing file or directory names. These
+      can be relative paths ("foo/bar.txt"), system absolute paths
+      ("/foo/bar.txt"), or source absolute paths ("//foo/bar.txt").
+
+  new_base
+      The directory to convert the paths to be relative to. This can be an
+      absolute path or a relative path (which will be treated as being relative
+      to the current BUILD-file's directory).
+
+      As a special case, if new_base is the empty string (the default), all
+      paths will be converted to system-absolute native style paths with system
+      path separators. This is useful for invoking external programs.
+
+  current_base
+      Directory representing the base for relative paths in the input. If this
+      is not an absolute path, it will be treated as being relative to the
+      current build file. Use "." (the default) to convert paths from the
+      current BUILD-file's directory.
+
+Return value
+
+  The return value will be the same type as the input value (either a string or
+  a list of strings). All relative and source-absolute file names will be
+  converted to be relative to the requested output System-absolute paths will
+  be unchanged.
+
+  Whether an output path will end in a slash will match whether the
+  corresponding input path ends in a slash. It will return "." or "./"
+  (depending on whether the input ends in a slash) to avoid returning empty
+  strings. This means if you want a root path ("//" or "/") not ending in a
+  slash, you can add a dot ("//.").
+
+Example
+
+  # Convert a file in the current directory to be relative to the build
+  # directory (the current dir when executing compilers and scripts).
+  foo = rebase_path("myfile.txt", root_build_dir)
+  # might produce "../../project/myfile.txt".
+
+  # Convert a file to be system absolute:
+  foo = rebase_path("myfile.txt")
+  # Might produce "D:\\source\\project\\myfile.txt" on Windows or
+  # "/home/you/source/project/myfile.txt" on Linux.
+
+  # Typical usage for converting to the build directory for a script.
+  action("myscript") {
+    # Don't convert sources, GN will automatically convert these to be relative
+    # to the build directory when it constructs the command line for your
+    # script.
+    sources = [ "foo.txt", "bar.txt" ]
+
+    # Extra file args passed manually need to be explicitly converted
+    # to be relative to the build directory:
+    args = [
+      "--data",
+      rebase_path("//mything/data/input.dat", root_build_dir),
+      "--rel",
+      rebase_path("relative_path.txt", root_build_dir)
+    ] + rebase_path(sources, root_build_dir)
+  }
+)";
+
+Value RunRebasePath(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    Err* err) {
+  Value result;
+
+  // Argument indices.
+  static const size_t kArgIndexInputs = 0;
+  static const size_t kArgIndexDest = 1;
+  static const size_t kArgIndexFrom = 2;
+
+  // Inputs.
+  if (args.size() < 1 || args.size() > 3) {
+    *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
+    return result;
+  }
+  const Value& inputs = args[kArgIndexInputs];
+
+  // To path.
+  bool convert_to_system_absolute = true;
+  SourceDir to_dir;
+  const SourceDir& current_dir = scope->GetSourceDir();
+  if (args.size() > kArgIndexDest) {
+    if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
+      return result;
+    if (!args[kArgIndexDest].string_value().empty()) {
+      to_dir = current_dir.ResolveRelativeDir(
+          args[kArgIndexDest], err,
+          scope->settings()->build_settings()->root_path_utf8());
+      if (err->has_error())
+        return Value();
+      convert_to_system_absolute = false;
+    }
+  }
+
+  // From path.
+  SourceDir from_dir;
+  if (args.size() > kArgIndexFrom) {
+    if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
+      return result;
+    from_dir = current_dir.ResolveRelativeDir(
+        args[kArgIndexFrom], err,
+        scope->settings()->build_settings()->root_path_utf8());
+    if (err->has_error())
+      return Value();
+  } else {
+    // Default to current directory if unspecified.
+    from_dir = current_dir;
+  }
+
+  // Path conversion.
+  if (inputs.type() == Value::STRING) {
+    return ConvertOnePath(scope, function, inputs, from_dir, to_dir,
+                          convert_to_system_absolute, err);
+
+  } else if (inputs.type() == Value::LIST) {
+    result = Value(function, Value::LIST);
+    result.list_value().reserve(inputs.list_value().size());
+
+    for (const auto& input : inputs.list_value()) {
+      result.list_value().push_back(
+          ConvertOnePath(scope, function, input, from_dir, to_dir,
+                         convert_to_system_absolute, err));
+      if (err->has_error()) {
+        result = Value();
+        return result;
+      }
+    }
+    return result;
+  }
+
+  *err = Err(function->function(), "rebase_path requires a list or a string.");
+  return result;
+}
+
+}  // namespace functions
diff --git a/src/gn/function_rebase_path_unittest.cc b/src/gn/function_rebase_path_unittest.cc
new file mode 100644 (file)
index 0000000..a4c403f
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+std::string RebaseOne(Scope* scope,
+                      const char* input,
+                      const char* to_dir,
+                      const char* from_dir) {
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, input));
+  args.push_back(Value(nullptr, to_dir));
+  args.push_back(Value(nullptr, from_dir));
+
+  Err err;
+  FunctionCallNode function;
+  Value result = functions::RunRebasePath(scope, &function, args, &err);
+  bool is_string = result.type() == Value::STRING;
+  EXPECT_TRUE(is_string);
+
+  return result.string_value();
+}
+
+}  // namespace
+
+TEST(RebasePath, Strings) {
+  TestWithScope setup;
+  Scope* scope = setup.scope();
+  scope->set_source_dir(SourceDir("//tools/gn/"));
+
+  // Build-file relative paths.
+  EXPECT_EQ("../../tools/gn", RebaseOne(scope, ".", "//out/Debug", "."));
+  EXPECT_EQ("../../tools/gn/", RebaseOne(scope, "./", "//out/Debug", "."));
+  EXPECT_EQ("../../tools/gn/foo", RebaseOne(scope, "foo", "//out/Debug", "."));
+  EXPECT_EQ("../..", RebaseOne(scope, "../..", "//out/Debug", "."));
+  EXPECT_EQ("../../", RebaseOne(scope, "../../", "//out/Debug", "."));
+
+  // Without a source root defined, we cannot move out of the source tree.
+  EXPECT_EQ("../..", RebaseOne(scope, "../../..", "//out/Debug", "."));
+
+  // Source-absolute input paths.
+  EXPECT_EQ("./", RebaseOne(scope, "//", "//", "//"));
+  EXPECT_EQ("foo", RebaseOne(scope, "//foo", "//", "//"));
+  EXPECT_EQ("foo/", RebaseOne(scope, "//foo/", "//", "//"));
+  EXPECT_EQ("../../foo/bar", RebaseOne(scope, "//foo/bar", "//out/Debug", "."));
+  EXPECT_EQ("./", RebaseOne(scope, "//foo/", "//foo/", "//"));
+  EXPECT_EQ(".", RebaseOne(scope, "//foo", "//foo", "//"));
+
+  // Test slash conversion.
+  EXPECT_EQ("foo/bar", RebaseOne(scope, "foo/bar", ".", "."));
+  EXPECT_EQ("foo/bar", RebaseOne(scope, "foo\\bar", ".", "."));
+
+// Test system path output.
+#if defined(OS_WIN)
+  setup.build_settings()->SetRootPath(base::FilePath(u"C:/path/to/src"));
+  EXPECT_EQ("C:/path/to/src", RebaseOne(scope, ".", "", "//"));
+  EXPECT_EQ("C:/path/to/src/", RebaseOne(scope, "//", "", "//"));
+  EXPECT_EQ("C:/path/to/src/foo", RebaseOne(scope, "foo", "", "//"));
+  EXPECT_EQ("C:/path/to/src/foo/", RebaseOne(scope, "foo/", "", "//"));
+  EXPECT_EQ("C:/path/to/src/tools/gn/foo", RebaseOne(scope, "foo", "", "."));
+  EXPECT_EQ("C:/path/to/other/tools",
+            RebaseOne(scope, "//../other/tools", "", "//"));
+  EXPECT_EQ("C:/path/to/src/foo/bar",
+            RebaseOne(scope, "//../src/foo/bar", "", "//"));
+  EXPECT_EQ("C:/path/to", RebaseOne(scope, "//..", "", "//"));
+  EXPECT_EQ("C:/path", RebaseOne(scope, "../../../..", "", "."));
+  EXPECT_EQ("C:/path/to/external/dir/",
+            RebaseOne(scope, "//../external/dir/", "", "//"));
+
+#else
+  setup.build_settings()->SetRootPath(base::FilePath("/path/to/src"));
+  EXPECT_EQ("/path/to/src", RebaseOne(scope, ".", "", "//"));
+  EXPECT_EQ("/path/to/src/", RebaseOne(scope, "//", "", "//"));
+  EXPECT_EQ("/path/to/src/foo", RebaseOne(scope, "foo", "", "//"));
+  EXPECT_EQ("/path/to/src/foo/", RebaseOne(scope, "foo/", "", "//"));
+  EXPECT_EQ("/path/to/src/tools/gn/foo", RebaseOne(scope, "foo", "", "."));
+  EXPECT_EQ("/path/to/other/tools",
+            RebaseOne(scope, "//../other/tools", "", "//"));
+  EXPECT_EQ("/path/to/src/foo/bar",
+            RebaseOne(scope, "//../src/foo/bar", "", "//"));
+  EXPECT_EQ("/path/to", RebaseOne(scope, "//..", "", "//"));
+  EXPECT_EQ("/path", RebaseOne(scope, "../../../..", "", "."));
+  EXPECT_EQ("/path/to/external/dir/",
+            RebaseOne(scope, "//../external/dir/", "", "//"));
+#endif
+}
+
+TEST(RebasePath, StringsSystemPaths) {
+  TestWithScope setup;
+  Scope* scope = setup.scope();
+
+#if defined(OS_WIN)
+  setup.build_settings()->SetBuildDir(SourceDir("C:/ssd/out/Debug"));
+  setup.build_settings()->SetRootPath(base::FilePath(u"C:/hdd/src"));
+
+  // Test system absolute to-dir.
+  EXPECT_EQ("../../ssd/out/Debug",
+            RebaseOne(scope, ".", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/",
+            RebaseOne(scope, "./", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo",
+            RebaseOne(scope, "foo", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo/",
+            RebaseOne(scope, "foo/", "//", "C:/ssd/out/Debug"));
+
+  // Test system absolute from-dir.
+  EXPECT_EQ("../../../hdd/src",
+            RebaseOne(scope, ".", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/",
+            RebaseOne(scope, "./", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo",
+            RebaseOne(scope, "foo", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo/",
+            RebaseOne(scope, "foo/", "C:/ssd/out/Debug", "//"));
+#else
+  setup.build_settings()->SetBuildDir(SourceDir("/ssd/out/Debug"));
+  setup.build_settings()->SetRootPath(base::FilePath("/ssd/out/Debug"));
+  EXPECT_EQ("../Debug-suffix/a", RebaseOne(scope, "/ssd/out/Debug-suffix/a",
+                                           "/ssd/out/Debug", "/ssd/out/Debug"));
+  setup.build_settings()->SetRootPath(base::FilePath("/hdd/src"));
+
+  // Test system absolute to-dir.
+  EXPECT_EQ("../../ssd/out/Debug",
+            RebaseOne(scope, ".", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/",
+            RebaseOne(scope, "./", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo",
+            RebaseOne(scope, "foo", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo/",
+            RebaseOne(scope, "foo/", "//", "/ssd/out/Debug"));
+
+  // Test system absolute from-dir.
+  EXPECT_EQ("../../../hdd/src", RebaseOne(scope, ".", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/",
+            RebaseOne(scope, "./", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo",
+            RebaseOne(scope, "foo", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo/",
+            RebaseOne(scope, "foo/", "/ssd/out/Debug", "//"));
+#endif
+}
+
+// Test list input.
+TEST(RebasePath, List) {
+  TestWithScope setup;
+  setup.scope()->set_source_dir(SourceDir("//gn/"));
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, Value::LIST));
+  args[0].list_value().push_back(Value(nullptr, "foo.txt"));
+  args[0].list_value().push_back(Value(nullptr, "bar.txt"));
+  args.push_back(Value(nullptr, "//out/Debug/"));
+  args.push_back(Value(nullptr, "."));
+
+  Err err;
+  FunctionCallNode function;
+  Value ret = functions::RunRebasePath(setup.scope(), &function, args, &err);
+  EXPECT_FALSE(err.has_error());
+
+  ASSERT_EQ(Value::LIST, ret.type());
+  ASSERT_EQ(2u, ret.list_value().size());
+
+  EXPECT_EQ("../../gn/foo.txt", ret.list_value()[0].string_value());
+  EXPECT_EQ("../../gn/bar.txt", ret.list_value()[1].string_value());
+}
+
+TEST(RebasePath, Errors) {
+  TestWithScope setup;
+
+  // No arg input should issue an error.
+  Err err;
+  std::vector<Value> args;
+  FunctionCallNode function;
+  Value ret = functions::RunRebasePath(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
diff --git a/src/gn/function_set_default_toolchain.cc b/src/gn/function_set_default_toolchain.cc
new file mode 100644 (file)
index 0000000..1369377
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/build_settings.h"
+#include "gn/functions.h"
+#include "gn/loader.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+
+namespace functions {
+
+const char kSetDefaultToolchain[] = "set_default_toolchain";
+const char kSetDefaultToolchain_HelpShort[] =
+    "set_default_toolchain: Sets the default toolchain name.";
+const char kSetDefaultToolchain_Help[] =
+    R"(set_default_toolchain: Sets the default toolchain name.
+
+  set_default_toolchain(toolchain_label)
+
+  The given label should identify a toolchain definition (see "gn help
+  toolchain"). This toolchain will be used for all targets unless otherwise
+  specified.
+
+  This function is only valid to call during the processing of the build
+  configuration file. Since the build configuration file is processed
+  separately for each toolchain, this function will be a no-op when called
+  under any non-default toolchains.
+
+  For example, the default toolchain should be appropriate for the current
+  environment. If the current environment is 32-bit and somebody references a
+  target with a 64-bit toolchain, we wouldn't want processing of the build
+  config file for the 64-bit toolchain to reset the default toolchain to
+  64-bit, we want to keep it 32-bits.
+
+Argument
+
+  toolchain_label
+      Toolchain name.
+
+Example
+
+  # Set default toolchain only has an effect when run in the context of the
+  # default toolchain. Pick the right one according to the current CPU
+  # architecture.
+  if (target_cpu == "x64") {
+    set_default_toolchain("//toolchains:64")
+  } else if (target_cpu == "x86") {
+    set_default_toolchain("//toolchains:32")
+  }
+)";
+
+Value RunSetDefaultToolchain(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err) {
+  if (!scope->IsProcessingBuildConfig()) {
+    *err = Err(
+        function->function(), "Must be called from build config.",
+        "set_default_toolchain can only be called from the build configuration "
+        "file.");
+    return Value();
+  }
+
+  // When the loader is expecting the default toolchain to be set, it will set
+  // this key on the scope to point to the destination.
+  Label* default_toolchain_dest = static_cast<Label*>(
+      scope->GetProperty(Loader::kDefaultToolchainKey, nullptr));
+  if (!default_toolchain_dest)
+    return Value();
+
+  const SourceDir& current_dir = scope->GetSourceDir();
+  const Label& default_toolchain = ToolchainLabelForScope(scope);
+
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+  Label toolchain_label(Label::Resolve(
+      current_dir, scope->settings()->build_settings()->root_path_utf8(),
+      default_toolchain, args[0], err));
+  if (toolchain_label.is_null())
+    return Value();
+
+  *default_toolchain_dest = toolchain_label;
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_set_defaults.cc b/src/gn/function_set_defaults.cc
new file mode 100644 (file)
index 0000000..031edc3
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+
+namespace functions {
+
+const char kSetDefaults[] = "set_defaults";
+const char kSetDefaults_HelpShort[] =
+    "set_defaults: Set default values for a target type.";
+const char kSetDefaults_Help[] =
+    R"(set_defaults: Set default values for a target type.
+
+  set_defaults(<target_type_name>) { <values...> }
+
+  Sets the default values for a given target type. Whenever target_type_name is
+  seen in the future, the values specified in set_default's block will be
+  copied into the current scope.
+
+  When the target type is used, the variable copying is very strict. If a
+  variable with that name is already in scope, the build will fail with an
+  error.
+
+  set_defaults can be used for built-in target types ("executable",
+  "shared_library", etc.) and custom ones defined via the "template" command.
+  It can be called more than once and the most recent call in any scope will
+  apply, but there is no way to refer to the previous defaults and modify them
+  (each call to set_defaults must supply a complete list of all defaults it
+  wants). If you want to share defaults, store them in a separate variable.
+
+Example
+
+  set_defaults("static_library") {
+    configs = [ "//tools/mything:settings" ]
+  }
+
+  static_library("mylib") {
+    # The configs will be auto-populated as above. You can remove it if
+    # you don't want the default for a particular default:
+    configs -= [ "//tools/mything:settings" ]
+  }
+)";
+
+Value RunSetDefaults(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err) {
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+  const std::string& target_type(args[0].string_value());
+
+  if (!block) {
+    FillNeedsBlockError(function, err);
+    return Value();
+  }
+
+  // Run the block for the rule invocation.
+  Scope block_scope(scope);
+  block->Execute(&block_scope, err);
+  if (err->has_error())
+    return Value();
+
+  // Now copy the values set on the scope we made into the free-floating one
+  // (with no containing scope) used to hold the target defaults.
+  Scope* dest = scope->MakeTargetDefaults(target_type);
+  block_scope.NonRecursiveMergeTo(dest, Scope::MergeOptions(), function,
+                                  "<SHOULD NOT FAIL>", err);
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_template.cc b/src/gn/function_template.cc
new file mode 100644 (file)
index 0000000..92d5419
--- /dev/null
@@ -0,0 +1,226 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/template.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kTemplate[] = "template";
+const char kTemplate_HelpShort[] = "template: Define a template rule.";
+const char kTemplate_Help[] =
+    R"(template: Define a template rule.
+
+  A template defines a custom name that acts like a function. It provides a way
+  to add to the built-in target types.
+
+  The template() function is used to declare a template. To invoke the
+  template, just use the name of the template like any other target type.
+
+  Often you will want to declare your template in a special file that other
+  files will import (see "gn help import") so your template rule can be shared
+  across build files.
+
+Variables and templates:
+
+  When you call template() it creates a closure around all variables currently
+  in scope with the code in the template block. When the template is invoked,
+  the closure will be executed.
+
+  When the template is invoked, the code in the caller is executed and passed
+  to the template code as an implicit "invoker" variable. The template uses
+  this to read state out of the invoking code.
+
+  One thing explicitly excluded from the closure is the "current directory"
+  against which relative file names are resolved. The current directory will be
+  that of the invoking code, since typically that code specifies the file
+  names. This means all files internal to the template should use absolute
+  names.
+
+  A template will typically forward some or all variables from the invoking
+  scope to a target that it defines. Often, such variables might be optional.
+  Use the pattern:
+
+    if (defined(invoker.deps)) {
+      deps = invoker.deps
+    }
+
+  The function forward_variables_from() provides a shortcut to forward one or
+  more or possibly all variables in this manner:
+
+    forward_variables_from(invoker, ["deps", "public_deps"])
+
+Target naming
+
+  Your template should almost always define a built-in target with the name the
+  template invoker specified. For example, if you have an IDL template and
+  somebody does:
+    idl("foo") {...
+  you will normally want this to expand to something defining a source_set or
+  static_library named "foo" (among other things you may need). This way, when
+  another target specifies a dependency on "foo", the static_library or
+  source_set will be linked.
+
+  It is also important that any other targets your template expands to have
+  unique names, or you will get collisions.
+
+  Access the invoking name in your template via the implicit "target_name"
+  variable. This should also be the basis for how other targets that a template
+  expands to ensure uniqueness.
+
+  A typical example would be a template that defines an action to generate some
+  source files, and a source_set to compile that source. Your template would
+  name the source_set "target_name" because that's what you want external
+  targets to depend on to link your code. And you would name the action
+  something like "${target_name}_action" to make it unique. The source set
+  would have a dependency on the action to make it run.
+
+Overriding builtin targets
+
+  You can use template to redefine a built-in target in which case your template
+  takes a precedence over the built-in one. All uses of the target from within
+  the template definition will refer to the built-in target which makes it
+  possible to extend the behavior of the built-in target:
+
+    template("shared_library") {
+      shared_library(shlib) {
+        forward_variables_from(invoker, "*")
+        ...
+      }
+    }
+
+Example of defining a template
+
+  template("my_idl") {
+    # Be nice and help callers debug problems by checking that the variables
+    # the template requires are defined. This gives a nice message rather than
+    # giving the user an error about an undefined variable in the file defining
+    # the template
+    #
+    # You can also use defined() to give default values to variables
+    # unspecified by the invoker.
+    assert(defined(invoker.sources),
+           "Need sources in $target_name listing the idl files.")
+
+    # Name of the intermediate target that does the code gen. This must
+    # incorporate the target name so it's unique across template
+    # instantiations.
+    code_gen_target_name = target_name + "_code_gen"
+
+    # Intermediate target to convert IDL to C source. Note that the name is
+    # based on the name the invoker of the template specified. This way, each
+    # time the template is invoked we get a unique intermediate action name
+    # (since all target names are in the global scope).
+    action_foreach(code_gen_target_name) {
+      # Access the scope defined by the invoker via the implicit "invoker"
+      # variable.
+      sources = invoker.sources
+
+      # Note that we need an absolute path for our script file name. The
+      # current directory when executing this code will be that of the invoker
+      # (this is why we can use the "sources" directly above without having to
+      # rebase all of the paths). But if we need to reference a script relative
+      # to the template file, we'll need to use an absolute path instead.
+      script = "//tools/idl/idl_code_generator.py"
+
+      # Tell GN how to expand output names given the sources.
+      # See "gn help source_expansion" for more.
+      outputs = [ "$target_gen_dir/{{source_name_part}}.cc",
+                  "$target_gen_dir/{{source_name_part}}.h" ]
+    }
+
+    # Name the source set the same as the template invocation so instancing
+    # this template produces something that other targets can link to in their
+    # deps.
+    source_set(target_name) {
+      # Generates the list of sources, we get these from the action_foreach
+      # above.
+      sources = get_target_outputs(":$code_gen_target_name")
+
+      # This target depends on the files produced by the above code gen target.
+      deps = [ ":$code_gen_target_name" ]
+    }
+  }
+
+Example of invoking the resulting template
+
+  # This calls the template code above, defining target_name to be
+  # "foo_idl_files" and "invoker" to be the set of stuff defined in the curly
+  # brackets.
+  my_idl("foo_idl_files") {
+    # Goes into the template as "invoker.sources".
+    sources = [ "foo.idl", "bar.idl" ]
+  }
+
+  # Here is a target that depends on our template.
+  executable("my_exe") {
+    # Depend on the name we gave the template call above. Internally, this will
+    # produce a dependency from executable to the source_set inside the
+    # template (since it has this name), which will in turn depend on the code
+    # gen action.
+    deps = [ ":foo_idl_files" ]
+  }
+)";
+
+Value RunTemplate(Scope* scope,
+                  const FunctionCallNode* function,
+                  const std::vector<Value>& args,
+                  BlockNode* block,
+                  Err* err) {
+  // Of course you can have configs and targets in a template. But here, we're
+  // not actually executing the block, only declaring it. Marking the template
+  // declaration as non-nestable means that you can't put it inside a target,
+  // for example.
+  NonNestableBlock non_nestable(scope, function, "template");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  // TODO(brettw) determine if the function is built-in and throw an error if
+  // it is.
+  if (args.size() != 1) {
+    *err =
+        Err(function->function(), "Need exactly one string arg to template.");
+    return Value();
+  }
+  if (!args[0].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  std::string template_name = args[0].string_value();
+
+  const Template* existing_template = scope->GetTemplate(template_name);
+  if (existing_template) {
+    *err = Err(function, "Duplicate template definition.",
+               "A template with this name was already defined.");
+    err->AppendSubErr(
+        Err(existing_template->GetDefinitionRange(), "Previous definition."));
+    return Value();
+  }
+
+  scope->AddTemplate(template_name, new Template(scope, function));
+
+  // The template object above created a closure around the variables in the
+  // current scope. The template code will execute in that context when it's
+  // invoked. But this means that any variables defined above that are used
+  // by the template won't get marked used just by defining the template. The
+  // result can be spurious unused variable errors.
+  //
+  // The "right" thing to do would be to walk the syntax tree inside the
+  // template, find all identifier references, and mark those variables used.
+  // This is annoying and error-prone to implement and takes extra time to run
+  // for this narrow use case.
+  //
+  // Templates are most often defined in .gni files which don't get
+  // used-variable checking anyway, and this case is annoying enough that the
+  // incremental value of unused variable checking isn't worth the
+  // alternatives. So all values in scope before this template definition are
+  // exempted from unused variable checking.
+  scope->MarkAllUsed();
+
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_template_unittest.cc b/src/gn/function_template_unittest.cc
new file mode 100644 (file)
index 0000000..2ea9e8b
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+// Checks that variables used inside template definitions aren't reported
+// unused if they were declared above the template.
+TEST(FunctionTemplate, MarkUsed) {
+  TestWithScope setup;
+  TestParseInput input(
+      "a = 1\n"  // Unused outside of template.
+      "template(\"templ\") {\n"
+      "  print(a)\n"
+      "}\n");
+  ASSERT_FALSE(input.has_error()) << input.parse_err().message();
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // Normally the loader calls CheckForUnusedVars() when it loads a file
+  // since normal blocks don't do this check. To avoid having to make this
+  // test much more complicated, just explicitly do the check to make sure
+  // things are marked properly.
+  setup.scope()->CheckForUnusedVars(&err);
+  EXPECT_FALSE(err.has_error());
+}
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
new file mode 100644 (file)
index 0000000..a6deec6
--- /dev/null
@@ -0,0 +1,928 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "gn/c_tool.h"
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/general_tool.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/tool.h"
+#include "gn/toolchain.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+namespace functions {
+
+namespace {
+
+// This is just a unique value to take the address of to use as the key for
+// the toolchain property on a scope.
+const int kToolchainPropertyKey = 0;
+
+}  // namespace
+
+// toolchain -------------------------------------------------------------------
+
+const char kToolchain[] = "toolchain";
+const char kToolchain_HelpShort[] = "toolchain: Defines a toolchain.";
+const char kToolchain_Help[] =
+    R"*(toolchain: Defines a toolchain.
+
+  A toolchain is a set of commands and build flags used to compile the source
+  code. The toolchain() function defines these commands.
+
+Toolchain overview
+
+  You can have more than one toolchain in use at once in a build and a target
+  can exist simultaneously in multiple toolchains. A build file is executed
+  once for each toolchain it is referenced in so the GN code can vary all
+  parameters of each target (or which targets exist) on a per-toolchain basis.
+
+  When you have a simple build with only one toolchain, the build config file
+  is loaded only once at the beginning of the build. It must call
+  set_default_toolchain() (see "gn help set_default_toolchain") to tell GN the
+  label of the toolchain definition to use. The "toolchain_args" section of the
+  toolchain definition is ignored.
+
+  When a target has a dependency on a target using different toolchain (see "gn
+  help labels" for how to specify this), GN will start a build using that
+  secondary toolchain to resolve the target. GN will load the build config file
+  with the build arguments overridden as specified in the toolchain_args.
+  Because the default toolchain is already known, calls to
+  set_default_toolchain() are ignored.
+
+  To load a file in an alternate toolchain, GN does the following:
+
+    1. Loads the file with the toolchain definition in it (as determined by the
+       toolchain label).
+    2. Re-runs the master build configuration file, applying the arguments
+       specified by the toolchain_args section of the toolchain definition.
+    3. Loads the destination build file in the context of the configuration file
+       in the previous step.
+
+  The toolchain configuration is two-way. In the default toolchain (i.e. the
+  main build target) the configuration flows from the build config file to the
+  toolchain. The build config file looks at the state of the build (OS type,
+  CPU architecture, etc.) and decides which toolchain to use (via
+  set_default_toolchain()). In secondary toolchains, the configuration flows
+  from the toolchain to the build config file: the "toolchain_args" in the
+  toolchain definition specifies the arguments to re-invoke the build.
+
+Functions and variables
+
+  tool()
+    The tool() function call specifies the commands to run for a given step. See
+    "gn help tool".
+
+  toolchain_args [scope]
+    Overrides for build arguments to pass to the toolchain when invoking it.
+    This is a variable of type "scope" where the variable names correspond to
+    variables in declare_args() blocks.
+
+    When you specify a target using an alternate toolchain, the master build
+    configuration file is re-interpreted in the context of that toolchain.
+    toolchain_args allows you to control the arguments passed into this
+    alternate invocation of the build.
+
+    Any default system arguments or arguments passed in via "gn args" will also
+    be passed to the alternate invocation unless explicitly overridden by
+    toolchain_args.
+
+    The toolchain_args will be ignored when the toolchain being defined is the
+    default. In this case, it's expected you want the default argument values.
+
+    See also "gn help buildargs" for an overview of these arguments.
+
+  propagates_configs [boolean, default=false]
+    Determines whether public_configs and all_dependent_configs in this
+    toolchain propagate to targets in other toolchains.
+
+    When false (the default), this toolchain will not propagate any configs to
+    targets in other toolchains that depend on it targets inside this
+    toolchain. This matches the most common usage of toolchains where they
+    represent different architectures or compilers and the settings that apply
+    to one won't necessarily apply to others.
+
+    When true, configs (public and all-dependent) will cross the boundary out
+    of this toolchain as if the toolchain boundary wasn't there. This only
+    affects one direction of dependencies: a toolchain can't control whether
+    it accepts such configs, only whether it pushes them. The build is
+    responsible for ensuring that any external targets depending on targets in
+    this toolchain are compatible with the compiler flags, etc. that may be
+    propagated.
+
+  deps [string list]
+    Dependencies of this toolchain. These dependencies will be resolved before
+    any target in the toolchain is compiled. To avoid circular dependencies
+    these must be targets defined in another toolchain.
+
+    This is expressed as a list of targets, and generally these targets will
+    always specify a toolchain:
+      deps = [ "//foo/bar:baz(//build/toolchain:bootstrap)" ]
+
+    This concept is somewhat inefficient to express in Ninja (it requires a lot
+    of duplicate of rules) so should only be used when absolutely necessary.
+
+Example of defining a toolchain
+
+  toolchain("32") {
+    tool("cc") {
+      command = "gcc {{source}}"
+      ...
+    }
+
+    toolchain_args = {
+      use_doom_melon = true  # Doom melon always required for 32-bit builds.
+      current_cpu = "x86"
+    }
+  }
+
+  toolchain("64") {
+    tool("cc") {
+      command = "gcc {{source}}"
+      ...
+    }
+
+    toolchain_args = {
+      # use_doom_melon is not overridden here, it will take the default.
+      current_cpu = "x64"
+    }
+  }
+
+Example of cross-toolchain dependencies
+
+  If a 64-bit target wants to depend on a 32-bit binary, it would specify a
+  dependency using data_deps (data deps are like deps that are only needed at
+  runtime and aren't linked, since you can't link a 32-bit and a 64-bit
+  library).
+
+    executable("my_program") {
+      ...
+      if (target_cpu == "x64") {
+        # The 64-bit build needs this 32-bit helper.
+        data_deps = [ ":helper(//toolchains:32)" ]
+      }
+    }
+
+    if (target_cpu == "x86") {
+      # Our helper library is only compiled in 32-bits.
+      shared_library("helper") {
+        ...
+      }
+    }
+)*";
+
+Value RunToolchain(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err) {
+  NonNestableBlock non_nestable(scope, function, "toolchain");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  if (!EnsureNotProcessingImport(function, scope, err) ||
+      !EnsureNotProcessingBuildConfig(function, scope, err))
+    return Value();
+
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+
+  // Note that we don't want to use MakeLabelForScope since that will include
+  // the toolchain name in the label, and toolchain labels don't themselves
+  // have toolchain names.
+  const SourceDir& input_dir = scope->GetSourceDir();
+  Label label(input_dir, args[0].string_value());
+  if (g_scheduler->verbose_logging())
+    g_scheduler->Log("Defining toolchain", label.GetUserVisibleName(false));
+
+  // This object will actually be copied into the one owned by the toolchain
+  // manager, but that has to be done in the lock.
+  std::unique_ptr<Toolchain> toolchain = std::make_unique<Toolchain>(
+      scope->settings(), label, scope->build_dependency_files());
+  toolchain->set_defined_from(function);
+  toolchain->visibility().SetPublic();
+
+  Scope block_scope(scope);
+  block_scope.SetProperty(&kToolchainPropertyKey, toolchain.get());
+  block->Execute(&block_scope, err);
+  block_scope.SetProperty(&kToolchainPropertyKey, nullptr);
+  if (err->has_error())
+    return Value();
+
+  // Read deps (if any).
+  const Value* deps_value = block_scope.GetValue(variables::kDeps, true);
+  if (deps_value) {
+    ExtractListOfLabels(scope->settings()->build_settings(), *deps_value,
+                        block_scope.GetSourceDir(),
+                        ToolchainLabelForScope(&block_scope),
+                        &toolchain->deps(), err);
+    if (err->has_error())
+      return Value();
+  }
+
+  // Read toolchain args (if any).
+  const Value* toolchain_args = block_scope.GetValue("toolchain_args", true);
+  if (toolchain_args) {
+    if (!toolchain_args->VerifyTypeIs(Value::SCOPE, err))
+      return Value();
+
+    Scope::KeyValueMap values;
+    toolchain_args->scope_value()->GetCurrentScopeValues(&values);
+    toolchain->args() = values;
+  }
+
+  // Read propagates_configs (if present).
+  const Value* propagates_configs =
+      block_scope.GetValue("propagates_configs", true);
+  if (propagates_configs) {
+    if (!propagates_configs->VerifyTypeIs(Value::BOOLEAN, err))
+      return Value();
+    toolchain->set_propagates_configs(propagates_configs->boolean_value());
+  }
+
+  if (!block_scope.CheckForUnusedVars(err))
+    return Value();
+
+  // Save this toolchain.
+  toolchain->ToolchainSetupComplete();
+  Scope::ItemVector* collector = scope->GetItemCollector();
+  if (!collector) {
+    *err = Err(function, "Can't define a toolchain in this context.");
+    return Value();
+  }
+  collector->push_back(std::move(toolchain));
+  return Value();
+}
+
+// tool ------------------------------------------------------------------------
+
+const char kTool[] = "tool";
+const char kTool_HelpShort[] = "tool: Specify arguments to a toolchain tool.";
+const char kTool_Help[] =
+    R"(tool: Specify arguments to a toolchain tool.
+
+Usage
+
+  tool(<tool type>) {
+    <tool variables...>
+  }
+
+Tool types
+
+    Compiler tools:
+      "cc": C compiler
+      "cxx": C++ compiler
+      "cxx_module": C++ compiler used for Clang .modulemap files
+      "objc": Objective C compiler
+      "objcxx": Objective C++ compiler
+      "rc": Resource compiler (Windows .rc files)
+      "asm": Assembler
+      "swift": Swift compiler driver
+
+    Linker tools:
+      "alink": Linker for static libraries (archives)
+      "solink": Linker for shared libraries
+      "link": Linker for executables
+
+    Other tools:
+      "stamp": Tool for creating stamp files
+      "copy": Tool to copy files.
+      "action": Defaults for actions
+
+    Platform specific tools:
+      "copy_bundle_data": [iOS, macOS] Tool to copy files in a bundle.
+      "compile_xcassets": [iOS, macOS] Tool to compile asset catalogs.
+
+    Rust tools:
+      "rust_bin": Tool for compiling Rust binaries
+      "rust_cdylib": Tool for compiling C-compatible dynamic libraries.
+      "rust_dylib": Tool for compiling Rust dynamic libraries.
+      "rust_macro": Tool for compiling Rust procedural macros.
+      "rust_rlib": Tool for compiling Rust libraries.
+      "rust_staticlib": Tool for compiling Rust static libraries.
+
+Tool variables
+
+    command  [string with substitutions]
+        Valid for: all tools except "action" (required)
+
+        The command to run.
+
+    command_launcher  [string]
+        Valid for: all tools except "action" (optional)
+
+        The prefix with which to launch the command (e.g. the path to a Goma or
+        CCache compiler launcher).
+
+        Note that this prefix will not be included in the compilation database or
+        IDE files generated from the build.
+
+    default_output_dir  [string with substitutions]
+        Valid for: linker tools
+
+        Default directory name for the output file relative to the
+        root_build_dir. It can contain other substitution patterns. This will
+        be the default value for the {{output_dir}} expansion (discussed below)
+        but will be overridden by the "output_dir" variable in a target, if one
+        is specified.
+
+        GN doesn't do anything with this string other than pass it along,
+        potentially with target-specific overrides. It is the tool's job to use
+        the expansion so that the files will be in the right place.
+
+    default_output_extension  [string]
+        Valid for: linker tools
+
+        Extension for the main output of a linkable tool. It includes the
+        leading dot. This will be the default value for the
+        {{output_extension}} expansion (discussed below) but will be overridden
+        by by the "output extension" variable in a target, if one is specified.
+        Empty string means no extension.
+
+        GN doesn't actually do anything with this extension other than pass it
+        along, potentially with target-specific overrides. One would typically
+        use the {{output_extension}} value in the "outputs" to read this value.
+
+        Example: default_output_extension = ".exe"
+
+    depfile  [string with substitutions]
+        Valid for: compiler tools (optional)
+
+        If the tool can write ".d" files, this specifies the name of the
+        resulting file. These files are used to list header file dependencies
+        (or other implicit input dependencies) that are discovered at build
+        time. See also "depsformat".
+
+        Example: depfile = "{{output}}.d"
+
+    depsformat  [string]
+        Valid for: compiler tools (when depfile is specified)
+
+        Format for the deps outputs. This is either "gcc" or "msvc". See the
+        ninja documentation for "deps" for more information.
+
+        Example: depsformat = "gcc"
+
+    description  [string with substitutions, optional]
+        Valid for: all tools
+
+        What to print when the command is run.
+
+        Example: description = "Compiling {{source}}"
+
+    exe_output_extension [string, optional, rust tools only]
+    rlib_output_extension [string, optional, rust tools only]
+    dylib_output_extension [string, optional, rust tools only]
+    cdylib_output_extension [string, optional, rust tools only]
+    rust_proc_macro_output_extension [string, optional, rust tools only]
+        Valid for: Rust tools
+
+        These specify the default tool output for each of the crate types.
+        The default is empty for executables, shared, and static libraries and
+        ".rlib" for rlibs. Note that the Rust compiler complains with an error
+        if external crates do not take the form `lib<name>.rlib` or
+        `lib<name>.<shared_extension>`, where `<shared_extension>` is `.so`,
+        `.dylib`, or `.dll` as appropriate for the platform.
+
+    lib_switch  [string, optional, link tools only]
+    lib_dir_switch  [string, optional, link tools only]
+        Valid for: Linker tools except "alink"
+
+        These strings will be prepended to the libraries and library search
+        directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          lib_switch = "-l"
+          lib_dir_switch = "-L"
+        then the "{{libs}}" expansion for
+          [ "freetype", "expat" ]
+        would be
+          "-lfreetype -lexpat".
+
+    framework_switch [string, optional, link tools only]
+    weak_framework_switch [string, optional, link tools only]
+    framework_dir_switch [string, optional, link tools only]
+        Valid for: Linker tools
+
+        These strings will be prepended to the frameworks and framework search
+        path directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          framework_switch = "-framework "
+          weak_framework_switch = "-weak_framework "
+          framework_dir_switch = "-F"
+        and:
+          framework_dirs = [ "$root_out_dir" ]
+          frameworks = [ "UIKit.framework", "Foo.framework" ]
+          weak_frameworks = [ "MediaPlayer.framework" ]
+        would be:
+          "-F. -framework UIKit -framework Foo -weak_framework MediaPlayer"
+
+    swiftmodule_switch [string, optional, link tools only]
+        Valid for: Linker tools except "alink"
+
+        The string will be prependend to the path to the .swiftmodule files
+        that are embedded in the linker output.
+
+        If you specified:
+          swiftmodule_swift = "-Wl,-add_ast_path,"
+        then the "{{swiftmodules}}" expansion for
+          [ "obj/foo/Foo.swiftmodule" ]
+        would be
+          "-Wl,-add_ast_path,obj/foo/Foo.swiftmodule"
+
+    outputs  [list of strings with substitutions]
+        Valid for: Linker and compiler tools (required)
+
+        An array of names for the output files the tool produces. These are
+        relative to the build output directory. There must always be at least
+        one output file. There can be more than one output (a linker might
+        produce a library and an import library, for example).
+
+        This array just declares to GN what files the tool will produce. It is
+        your responsibility to specify the tool command that actually produces
+        these files.
+
+        If you specify more than one output for shared library links, you
+        should consider setting link_output, depend_output, and
+        runtime_outputs.
+
+        Example for a compiler tool that produces .obj files:
+          outputs = [
+            "{{source_out_dir}}/{{source_name_part}}.obj"
+          ]
+
+        Example for a linker tool that produces a .dll and a .lib. The use of
+        {{target_output_name}}, {{output_extension}} and {{output_dir}} allows
+        the target to override these values.
+          outputs = [
+            "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+            "{{output_dir}}/{{target_output_name}}.lib",
+          ]
+
+    partial_outputs  [list of strings with substitutions]
+        Valid for: "swift" only
+
+        An array of names for the partial outputs the tool produces. These
+        are relative to the build output directory. The expansion will be
+        evaluated for each file listed in the "sources" of the target.
+
+        This is used to deal with whole module optimization, allowing to
+        list one object file per source file when whole module optimization
+        is disabled.
+
+    pool [label, optional]
+        Valid for: all tools (optional)
+
+        Label of the pool to use for the tool. Pools are used to limit the
+        number of tasks that can execute concurrently during the build.
+
+        See also "gn help pool".
+
+    link_output  [string with substitutions]
+    depend_output  [string with substitutions]
+        Valid for: "solink" only (optional)
+
+        These two files specify which of the outputs from the solink tool
+        should be used for linking and dependency tracking. These should match
+        entries in the "outputs". If unspecified, the first item in the
+        "outputs" array will be used for all. See "Separate linking and
+        dependencies for shared libraries" below for more.
+
+        On Windows, where the tools produce a .dll shared library and a .lib
+        import library, you will want the first two to be the import library
+        and the third one to be the .dll file. On Linux, if you're not doing
+        the separate linking/dependency optimization, all of these should be
+        the .so output.
+
+    output_prefix  [string]
+        Valid for: Linker tools (optional)
+
+        Prefix to use for the output name. Defaults to empty. This prefix will
+        be prepended to the name of the target (or the output_name if one is
+        manually specified for it) if the prefix is not already there. The
+        result will show up in the {{output_name}} substitution pattern.
+
+        Individual targets can opt-out of the output prefix by setting:
+          output_prefix_override = true
+        (see "gn help output_prefix_override").
+
+        This is typically used to prepend "lib" to libraries on
+        Posix systems:
+          output_prefix = "lib"
+
+    precompiled_header_type  [string]
+        Valid for: "cc", "cxx", "objc", "objcxx"
+
+        Type of precompiled headers. If undefined or the empty string,
+        precompiled headers will not be used for this tool. Otherwise use "gcc"
+        or "msvc".
+
+        For precompiled headers to be used for a given target, the target (or a
+        config applied to it) must also specify a "precompiled_header" and, for
+        "msvc"-style headers, a "precompiled_source" value. If the type is
+        "gcc", then both "precompiled_header" and "precompiled_source" must
+        resolve to the same file, despite the different formats required for
+        each."
+
+        See "gn help precompiled_header" for more.
+
+    restat  [boolean]
+        Valid for: all tools (optional, defaults to false)
+
+        Requests that Ninja check the file timestamp after this tool has run to
+        determine if anything changed. Set this if your tool has the ability to
+        skip writing output if the output file has not changed.
+
+        Normally, Ninja will assume that when a tool runs the output be new and
+        downstream dependents must be rebuild. When this is set to trye, Ninja
+        can skip rebuilding downstream dependents for input changes that don't
+        actually affect the output.
+
+        Example:
+          restat = true
+
+    rspfile  [string with substitutions]
+        Valid for: all tools except "action" (optional)
+
+        Name of the response file. If empty, no response file will be
+        used. See "rspfile_content".
+
+    rspfile_content  [string with substitutions]
+        Valid for: all tools except "action" (required when "rspfile" is used)
+
+        The contents to be written to the response file. This may include all
+        or part of the command to send to the tool which allows you to get
+        around OS command-line length limits.
+
+        This example adds the inputs and libraries to a response file, but
+        passes the linker flags directly on the command line:
+          tool("link") {
+            command = "link -o {{output}} {{ldflags}} @{{output}}.rsp"
+            rspfile = "{{output}}.rsp"
+            rspfile_content = "{{inputs}} {{solibs}} {{libs}} {{rlibs}}"
+          }
+
+    runtime_outputs  [string list with substitutions]
+        Valid for: linker tools
+
+        If specified, this list is the subset of the outputs that should be
+        added to runtime deps (see "gn help runtime_deps"). By default (if
+        runtime_outputs is empty or unspecified), it will be the link_output.
+
+)"  // String break to prevent overflowing the 16K max VC string length.
+    R"(Expansions for tool variables
+
+  All paths are relative to the root build directory, which is the current
+  directory for running all tools. These expansions are available to all tools:
+
+    {{label}}
+        The label of the current target. This is typically used in the
+        "description" field for link tools. The toolchain will be omitted from
+        the label for targets in the default toolchain, and will be included
+        for targets in other toolchains.
+
+    {{label_name}}
+        The short name of the label of the target. This is the part after the
+        colon. For "//foo/bar:baz" this will be "baz". Unlike
+        {{target_output_name}}, this is not affected by the "output_prefix" in
+        the tool or the "output_name" set on the target.
+
+    {{label_no_toolchain}}
+        The label of the current target, never including the toolchain
+        (otherwise, this is identical to {{label}}). This is used as the module
+        name when using .modulemap files.
+
+    {{output}}
+        The relative path and name of the output(s) of the current build step.
+        If there is more than one output, this will expand to a list of all of
+        them. Example: "out/base/my_file.o"
+
+    {{target_gen_dir}}
+    {{target_out_dir}}
+        The directory of the generated file and output directories,
+        respectively, for the current target. There is no trailing slash. See
+        also {{output_dir}} for linker tools. Example: "out/base/test"
+
+    {{target_output_name}}
+        The short name of the current target with no path information, or the
+        value of the "output_name" variable if one is specified in the target.
+        This will include the "output_prefix" if any. See also {{label_name}}.
+
+        Example: "libfoo" for the target named "foo" and an output prefix for
+        the linker tool of "lib".
+
+  Compiler tools have the notion of a single input and a single output, along
+  with a set of compiler-specific flags. The following expansions are
+  available:
+
+    {{asmflags}}
+    {{cflags}}
+    {{cflags_c}}
+    {{cflags_cc}}
+    {{cflags_objc}}
+    {{cflags_objcc}}
+    {{defines}}
+    {{include_dirs}}
+        Strings correspond that to the processed flags/defines/include
+        directories specified for the target.
+        Example: "--enable-foo --enable-bar"
+
+        Defines will be prefixed by "-D" and include directories will be
+        prefixed by "-I" (these work with Posix tools as well as Microsoft
+        ones).
+
+    {{module_deps}}
+    {{module_deps_no_self}}
+        Strings that correspond to the flags necessary to depend upon the Clang
+        modules referenced by the current target. The "_no_self" version doesn't
+        include the module for the current target, and can be used to compile
+        the pcm itself.
+
+    {{source}}
+        The relative path and name of the current input file.
+        Example: "../../base/my_file.cc"
+
+    {{source_file_part}}
+        The file part of the source including the extension (with no directory
+        information).
+        Example: "foo.cc"
+
+    {{source_name_part}}
+        The filename part of the source file with no directory or extension.
+        Example: "foo"
+
+    {{source_gen_dir}}
+    {{source_out_dir}}
+        The directory in the generated file and output directories,
+        respectively, for the current input file. If the source file is in the
+        same directory as the target is declared in, they will will be the same
+        as the "target" versions above. Example: "gen/base/test"
+
+  Linker tools have multiple inputs and (potentially) multiple outputs. The
+  static library tool ("alink") is not considered a linker tool. The following
+  expansions are available:
+
+    {{inputs}}
+    {{inputs_newline}}
+        Expands to the inputs to the link step. This will be a list of object
+        files and static libraries.
+        Example: "obj/foo.o obj/bar.o obj/somelibrary.a"
+
+        The "_newline" version will separate the input files with newlines
+        instead of spaces. This is useful in response files: some linkers can
+        take a "-filelist" flag which expects newline separated files, and some
+        Microsoft tools have a fixed-sized buffer for parsing each line of a
+        response file.
+
+    {{ldflags}}
+        Expands to the processed set of ldflags and library search paths
+        specified for the target.
+        Example: "-m64 -fPIC -pthread -L/usr/local/mylib"
+
+    {{libs}}
+        Expands to the list of system libraries to link to. Each will be
+        prefixed by the "lib_switch".
+
+        Example: "-lfoo -lbar"
+
+    {{output_dir}}
+        The value of the "output_dir" variable in the target, or the the value
+        of the "default_output_dir" value in the tool if the target does not
+        override the output directory. This will be relative to the
+        root_build_dir and will not end in a slash. Will be "." for output to
+        the root_build_dir.
+
+        This is subtly different than {{target_out_dir}} which is defined by GN
+        based on the target's path and not overridable. {{output_dir}} is for
+        the final output, {{target_out_dir}} is generally for object files and
+        other outputs.
+
+        Usually {{output_dir}} would be defined in terms of either
+        {{target_out_dir}} or {{root_out_dir}}
+
+    {{output_extension}}
+        The value of the "output_extension" variable in the target, or the
+        value of the "default_output_extension" value in the tool if the target
+        does not specify an output extension.
+        Example: ".so"
+
+    {{solibs}}
+        Extra libraries from shared library dependencies not specified in the
+        {{inputs}}. This is the list of link_output files from shared libraries
+        (if the solink tool specifies a "link_output" variable separate from
+        the "depend_output").
+
+        These should generally be treated the same as libs by your tool.
+
+        Example: "libfoo.so libbar.so"
+
+    {{rlibs}}
+        Any Rust .rlibs which need to be linked into a final C++ target.
+        These should be treated as {{inputs}} except that sometimes
+        they might have different linker directives applied.
+
+        Example: "obj/foo/libfoo.rlib"
+
+    {{frameworks}}
+        Shared libraries packaged as framework bundle. This is principally
+        used on Apple's platforms (macOS and iOS). All name must be ending
+        with ".framework" suffix; the suffix will be stripped when expanding
+        {{frameworks}} and each item will be preceded by "-framework" or
+        "-weak_framework".
+
+    {{swiftmodules}}
+        Swift .swiftmodule files that needs to be embedded into the binary.
+        This is necessary to correctly link with object generated by the
+        Swift compiler (the .swiftmodule file cannot be embedded in object
+        files directly). Those will be prefixed with "swiftmodule_switch"
+        value.
+
+)"  // String break to prevent overflowing the 16K max VC string length.
+    R"(  The static library ("alink") tool allows {{arflags}} plus the common tool
+  substitutions.
+
+  The copy tool allows the common compiler/linker substitutions, plus
+  {{source}} which is the source of the copy. The stamp tool allows only the
+  common tool substitutions.
+
+  The copy_bundle_data and compile_xcassets tools only allows the common tool
+  substitutions. Both tools are required to create iOS/macOS bundles and need
+  only be defined on those platforms.
+
+  The copy_bundle_data tool will be called with one source and needs to copy
+  (optionally optimizing the data representation) to its output. It may be
+  called with a directory as input and it needs to be recursively copied.
+
+  The compile_xcassets tool will be called with one or more source (each an
+  asset catalog) that needs to be compiled to a single output. The following
+  substitutions are available:
+
+    {{inputs}}
+        Expands to the list of .xcassets to use as input to compile the asset
+        catalog.
+
+    {{bundle_product_type}}
+        Expands to the product_type of the bundle that will contain the
+        compiled asset catalog. Usually corresponds to the product_type
+        property of the corresponding create_bundle target.
+
+    {{bundle_partial_info_plist}}
+        Expands to the path to the partial Info.plist generated by the
+        assets catalog compiler. Usually based on the target_name of
+        the create_bundle target.
+
+    {{xcasset_compiler_flags}}
+        Expands to the list of flags specified in corresponding
+        create_bundle target.
+
+  The Swift tool has multiple input and outputs. It must have exactly one
+  output of .swiftmodule type, but can have one or more object file outputs,
+  in addition to other type of outputs. The following expansions are available:
+
+    {{module_name}}
+        Expands to the string representing the module name of target under
+        compilation (see "module_name" variable).
+
+    {{module_dirs}}
+        Expands to the list of -I<path> for the target Swift module search
+        path computed from target dependencies.
+
+    {{swiftflags}}
+        Expands to the list of strings representing Swift compiler flags.
+
+  Rust tools have the notion of a single input and a single output, along
+  with a set of compiler-specific flags. The following expansions are
+  available:
+
+    {{crate_name}}
+        Expands to the string representing the crate name of target under
+        compilation.
+
+    {{crate_type}}
+        Expands to the string representing the type of crate for the target
+        under compilation.
+
+    {{externs}}
+        Expands to the list of --extern flags needed to include addition Rust
+        libraries in this target. Includes any specified renamed dependencies.
+
+    {{rustdeps}}
+        Expands to the list of -Ldependency=<path> strings needed to compile
+        this target.
+
+    {{rustenv}}
+        Expands to the list of environment variables.
+
+    {{rustflags}}
+        Expands to the list of strings representing Rust compiler flags.
+
+Separate linking and dependencies for shared libraries
+
+  Shared libraries are special in that not all changes to them require that
+  dependent targets be re-linked. If the shared library is changed but no
+  imports or exports are different, dependent code needn't be relinked, which
+  can speed up the build.
+
+  If your link step can output a list of exports from a shared library and
+  writes the file only if the new one is different, the timestamp of this file
+  can be used for triggering re-links, while the actual shared library would be
+  used for linking.
+
+  You will need to specify
+    restat = true
+  in the linker tool to make this work, so Ninja will detect if the timestamp
+  of the dependency file has changed after linking (otherwise it will always
+  assume that running a command updates the output):
+
+    tool("solink") {
+      command = "..."
+      outputs = [
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC",
+      ]
+      link_output =
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      depend_output =
+        "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC"
+      restat = true
+    }
+
+Example
+
+  toolchain("my_toolchain") {
+    # Put these at the top to apply to all tools below.
+    lib_switch = "-l"
+    lib_dir_switch = "-L"
+
+    tool("cc") {
+      command = "gcc {{source}} -o {{output}}"
+      outputs = [ "{{source_out_dir}}/{{source_name_part}}.o" ]
+      description = "GCC {{source}}"
+    }
+    tool("cxx") {
+      command = "g++ {{source}} -o {{output}}"
+      outputs = [ "{{source_out_dir}}/{{source_name_part}}.o" ]
+      description = "G++ {{source}}"
+    }
+  };
+)";
+
+Value RunTool(Scope* scope,
+              const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              BlockNode* block,
+              Err* err) {
+  // Find the toolchain definition we're executing inside of. The toolchain
+  // function will set a property pointing to it that we'll pick up.
+  Toolchain* toolchain = reinterpret_cast<Toolchain*>(
+      scope->GetProperty(&kToolchainPropertyKey, nullptr));
+  if (!toolchain) {
+    *err = Err(function->function(), "tool() called outside of toolchain().",
+               "The tool() function can only be used inside a toolchain() "
+               "definition.");
+    return Value();
+  }
+
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+  const std::string& tool_name = args[0].string_value();
+
+  // Run the tool block.
+  Scope block_scope(scope);
+  block->Execute(&block_scope, err);
+  if (err->has_error())
+    return Value();
+
+  std::unique_ptr<Tool> tool =
+      Tool::CreateTool(function, tool_name, &block_scope, toolchain, err);
+
+  if (!tool) {
+    return Value();
+  }
+
+  tool->set_defined_from(function);
+  toolchain->SetTool(std::move(tool));
+
+  // Make sure there weren't any vars set in this tool that were unused.
+  if (!block_scope.CheckForUnusedVars(err))
+    return Value();
+
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_toolchain_unittest.cc b/src/gn/function_toolchain_unittest.cc
new file mode 100644 (file)
index 0000000..74cbef9
--- /dev/null
@@ -0,0 +1,159 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+#include "gn/rust_tool.h"
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using FunctionToolchain = TestWithScheduler;
+
+TEST_F(FunctionToolchain, NoArguments) {
+  TestWithScope setup;
+
+  // Check that creating a toolchain with no name reports an error.
+  {
+    TestParseInput input(R"(toolchain() {})");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << err.message();
+  }
+
+  // Check that creating a toolchain with too many arguments is an error.
+  {
+    TestParseInput input(R"(toolchain("too", "many", "arguments") {})");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << err.message();
+  }
+}
+
+TEST_F(FunctionToolchain, RuntimeOutputs) {
+  TestWithScope setup;
+
+  // These runtime outputs are a subset of the outputs so are OK.
+  {
+    TestParseInput input(
+        R"(toolchain("good") {
+          tool("link") {
+            command = "link"
+            outputs = [ "foo" ]
+            runtime_outputs = [ "foo" ]
+          }
+        })");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << err.message();
+
+    // It should have generated a toolchain.
+    ASSERT_EQ(1u, setup.items().size());
+    const Toolchain* toolchain = setup.items()[0]->AsToolchain();
+    ASSERT_TRUE(toolchain);
+
+    // The toolchain should have a link tool with the two outputs.
+    const Tool* link = toolchain->GetTool(CTool::kCToolLink);
+    ASSERT_TRUE(link);
+    ASSERT_EQ(1u, link->outputs().list().size());
+    EXPECT_EQ("foo", link->outputs().list()[0].AsString());
+    ASSERT_EQ(1u, link->runtime_outputs().list().size());
+    EXPECT_EQ("foo", link->runtime_outputs().list()[0].AsString());
+  }
+
+  // This one is not a subset so should throw an error.
+  {
+    TestParseInput input(
+        R"(toolchain("bad") {
+          tool("link") {
+            outputs = [ "foo" ]
+            runtime_outputs = [ "bar" ]
+          }
+        })");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << err.message();
+  }
+}
+
+TEST_F(FunctionToolchain, Rust) {
+  TestWithScope setup;
+
+  // These runtime outputs are a subset of the outputs so are OK.
+  {
+    TestParseInput input(
+        R"(toolchain("rust") {
+          tool("rust_bin") {
+            command = "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin {{rustflags}} -o {{output}} {{externs}} {{source}}"
+            description = "RUST {{output}}"
+          }
+        })");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << err.message();
+
+    // It should have generated a toolchain.
+    ASSERT_EQ(1u, setup.items().size());
+    const Toolchain* toolchain = setup.items()[0]->AsToolchain();
+    ASSERT_TRUE(toolchain);
+
+    const Tool* rust = toolchain->GetTool(RustTool::kRsToolBin);
+    ASSERT_TRUE(rust);
+    ASSERT_EQ(rust->command().AsString(),
+              "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin "
+              "{{rustflags}} -o {{output}} {{externs}} {{source}}");
+    ASSERT_EQ(rust->description().AsString(), "RUST {{output}}");
+  }
+}
+
+TEST_F(FunctionToolchain, Command) {
+  TestWithScope setup;
+
+  TestParseInput input(
+      R"(toolchain("missing_command") {
+        tool("cxx") {}
+      })");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error()) << err.message();
+}
+
+TEST_F(FunctionToolchain, CommandLauncher) {
+  TestWithScope setup;
+
+  TestParseInput input(
+      R"(toolchain("good") {
+        tool("cxx") {
+          command = "cxx"
+          command_launcher = "/usr/goma/gomacc"
+        }
+      })");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // It should have generated a toolchain.
+  ASSERT_EQ(1u, setup.items().size());
+  const Toolchain* toolchain = setup.items()[0]->AsToolchain();
+  ASSERT_TRUE(toolchain);
+
+  // The toolchain should have a link tool with the two outputs.
+  const Tool* link = toolchain->GetTool(CTool::kCToolCxx);
+  ASSERT_TRUE(link);
+  EXPECT_EQ("/usr/goma/gomacc", link->command_launcher());
+}
diff --git a/src/gn/function_write_file.cc b/src/gn/function_write_file.cc
new file mode 100644 (file)
index 0000000..2c568c0
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "base/files/file_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/input_file.h"
+#include "gn/output_conversion.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "util/build_config.h"
+
+namespace functions {
+
+const char kWriteFile[] = "write_file";
+const char kWriteFile_HelpShort[] = "write_file: Write a file to disk.";
+const char kWriteFile_Help[] =
+    R"(write_file: Write a file to disk.
+
+  write_file(filename, data, output_conversion = "")
+
+  If data is a list, the list will be written one-item-per-line with no quoting
+  or brackets.
+
+  If the file exists and the contents are identical to that being written, the
+  file will not be updated. This will prevent unnecessary rebuilds of targets
+  that depend on this file.
+
+  One use for write_file is to write a list of inputs to an script that might
+  be too long for the command line. However, it is preferable to use response
+  files for this purpose. See "gn help response_file_contents".
+
+Arguments
+
+  filename
+      Filename to write. This must be within the output directory.
+
+  data
+      The list or string to write.
+
+  output_conversion
+    Controls how the output is written. See "gn help io_conversion".
+)";
+
+Value RunWriteFile(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   Err* err) {
+  if (args.size() != 3 && args.size() != 2) {
+    *err = Err(function->function(), "Wrong number of arguments to write_file",
+               "I expected two or three arguments.");
+    return Value();
+  }
+
+  // Compute the file name and make sure it's in the output dir.
+  const SourceDir& cur_dir = scope->GetSourceDir();
+  SourceFile source_file = cur_dir.ResolveRelativeFile(
+      args[0], err, scope->settings()->build_settings()->root_path_utf8());
+  if (err->has_error())
+    return Value();
+  if (!EnsureStringIsInOutputDir(
+          scope->settings()->build_settings()->build_dir(), source_file.value(),
+          args[0].origin(), err))
+    return Value();
+  g_scheduler->AddWrittenFile(source_file);  // Track that we wrote this file.
+
+  // Track how to recreate this file, since we write it a gen time.
+  // Note this is a hack since the correct output is not a dependency proper,
+  // but an addition of this file to the output of the gn rule that writes it.
+  // This dependency will, however, cause the gen step to be re-run and the
+  // build restarted if the file is missing.
+  g_scheduler->AddGenDependency(
+      scope->settings()->build_settings()->GetFullPath(source_file));
+
+  // Extract conversion value.
+  Value output_conversion;
+  if (args.size() != 3)
+    output_conversion = Value();
+  else
+    output_conversion = args[2];
+
+  // Compute output.
+  std::ostringstream contents;
+  ConvertValueToOutput(scope->settings(), args[1], output_conversion, contents,
+                       err);
+  if (err->has_error())
+    return Value();
+
+  base::FilePath file_path =
+      scope->settings()->build_settings()->GetFullPath(source_file);
+
+  // Make sure we're not replacing the same contents.
+  if (!WriteFileIfChanged(file_path, contents.str(), err))
+    *err = Err(function->function(), err->message(), err->help_text());
+
+  return Value();
+}
+
+}  // namespace functions
diff --git a/src/gn/function_write_file_unittest.cc b/src/gn/function_write_file_unittest.cc
new file mode 100644 (file)
index 0000000..5ee6123
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "gn/functions.h"
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_HAIKU) || defined(OS_MSYS)
+#include <sys/time.h>
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace {
+
+// Returns true on success, false if write_file signaled an error.
+bool CallWriteFile(Scope* scope,
+                   const std::string& filename,
+                   const Value& data) {
+  Err err;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, filename));
+  args.push_back(data);
+
+  FunctionCallNode function_call;
+  Value result = functions::RunWriteFile(scope, &function_call, args, &err);
+  EXPECT_EQ(Value::NONE, result.type());  // Should always return none.
+
+  return !err.has_error();
+}
+
+}  // namespace
+
+using WriteFileTest = TestWithScheduler;
+
+TEST_F(WriteFileTest, WithData) {
+  TestWithScope setup;
+
+  // Make a real directory for writing the files.
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  setup.build_settings()->SetRootPath(temp_dir.GetPath());
+  setup.build_settings()->SetBuildDir(SourceDir("//out/"));
+
+  Value some_string(nullptr, "some string contents");
+
+  // Should refuse to write files outside of the output dir.
+  EXPECT_FALSE(CallWriteFile(setup.scope(), "//in_root.txt", some_string));
+  EXPECT_FALSE(
+      CallWriteFile(setup.scope(), "//other_dir/foo.txt", some_string));
+
+  // Should be able to write to a new dir inside the out dir.
+  EXPECT_TRUE(CallWriteFile(setup.scope(), "//out/foo.txt", some_string));
+  base::FilePath foo_name = temp_dir.GetPath()
+                                .Append(FILE_PATH_LITERAL("out"))
+                                .Append(FILE_PATH_LITERAL("foo.txt"));
+  std::string result_contents;
+  EXPECT_TRUE(base::ReadFileToString(foo_name, &result_contents));
+  EXPECT_EQ(some_string.string_value(), result_contents);
+
+  // Update the contents with a list of a string and a number.
+  Value some_list(nullptr, Value::LIST);
+  some_list.list_value().push_back(Value(nullptr, "line 1"));
+  some_list.list_value().push_back(Value(nullptr, static_cast<int64_t>(2)));
+  EXPECT_TRUE(CallWriteFile(setup.scope(), "//out/foo.txt", some_list));
+  EXPECT_TRUE(base::ReadFileToString(foo_name, &result_contents));
+  EXPECT_EQ("line 1\n2\n", result_contents);
+
+  // Test that the file is not rewritten if the contents are not changed.
+  base::File foo_file(foo_name, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                                    base::File::FLAG_WRITE);
+  ASSERT_TRUE(foo_file.IsValid());
+
+  // Start by setting the modified time to something old to avoid clock
+  // resolution issues.
+#if defined(OS_WIN)
+  FILETIME last_access_filetime = {};
+  FILETIME last_modified_filetime = {};
+  ASSERT_TRUE(::SetFileTime(foo_file.GetPlatformFile(), nullptr,
+                            &last_access_filetime, &last_modified_filetime));
+#elif defined(OS_AIX) || defined(OS_HAIKU) || defined(OS_SOLARIS)
+  struct timeval times[2] = {};
+  ASSERT_EQ(utimes(foo_name.value().c_str(), times), 0);
+#else
+  struct timeval times[2] = {};
+  ASSERT_EQ(futimes(foo_file.GetPlatformFile(), times), 0);
+#endif
+
+  // Read the current time to avoid timer resolution issues when comparing
+  // below.
+  base::File::Info original_info;
+  foo_file.GetInfo(&original_info);
+
+  EXPECT_TRUE(CallWriteFile(setup.scope(), "//out/foo.txt", some_list));
+
+  // Verify that the last modified time is the same as before.
+  base::File::Info new_info;
+  foo_file.GetInfo(&new_info);
+  EXPECT_EQ(original_info.last_modified, new_info.last_modified);
+}
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
new file mode 100644 (file)
index 0000000..6acf41a
--- /dev/null
@@ -0,0 +1,1498 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+
+#include <stddef.h>
+#include <cctype>
+#include <memory>
+#include <utility>
+
+#include "base/environment.h"
+#include "base/strings/string_util.h"
+#include "gn/build_settings.h"
+#include "gn/config.h"
+#include "gn/config_values_generator.h"
+#include "gn/err.h"
+#include "gn/input_file.h"
+#include "gn/parse_node_value_adapter.h"
+#include "gn/parse_tree.h"
+#include "gn/pool.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/template.h"
+#include "gn/token.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+namespace {
+
+// Some functions take a {} following them, and some don't. For the ones that
+// don't, this is used to verify that the given block node is null and will
+// set the error accordingly if it's not. Returns true if the block is null.
+bool VerifyNoBlockForFunctionCall(const FunctionCallNode* function,
+                                  const BlockNode* block,
+                                  Err* err) {
+  if (!block)
+    return true;
+
+  *err =
+      Err(block, "Unexpected '{'.",
+          "This function call doesn't take a {} block following it, and you\n"
+          "can't have a {} block that's not connected to something like an if\n"
+          "statement or a target declaration.");
+  err->AppendRange(function->function().range());
+  return false;
+}
+
+// This key is set as a scope property on the scope of a declare_args() block,
+// in order to prevent reading a variable defined earlier in the same call
+// (see `gn help declare_args` for more).
+const void* kInDeclareArgsKey = nullptr;
+
+}  // namespace
+
+bool EnsureNotReadingFromSameDeclareArgs(const ParseNode* node,
+                                         const Scope* cur_scope,
+                                         const Scope* val_scope,
+                                         Err* err) {
+  // If the value didn't come from a scope at all, we're safe.
+  if (!val_scope)
+    return true;
+
+  const Scope* val_args_scope = nullptr;
+  val_scope->GetProperty(&kInDeclareArgsKey, &val_args_scope);
+
+  const Scope* cur_args_scope = nullptr;
+  cur_scope->GetProperty(&kInDeclareArgsKey, &cur_args_scope);
+  if (!val_args_scope || !cur_args_scope || (val_args_scope != cur_args_scope))
+    return true;
+
+  *err =
+      Err(node,
+          "Reading a variable defined in the same declare_args() call.\n"
+          "\n"
+          "If you need to set the value of one arg based on another, put\n"
+          "them in two separate declare_args() calls, one after the other.\n");
+  return false;
+}
+
+bool EnsureNotProcessingImport(const ParseNode* node,
+                               const Scope* scope,
+                               Err* err) {
+  if (scope->IsProcessingImport()) {
+    *err =
+        Err(node, "Not valid from an import.",
+            "Imports are for defining defaults, variables, and rules. The\n"
+            "appropriate place for this kind of thing is really in a normal\n"
+            "BUILD file.");
+    return false;
+  }
+  return true;
+}
+
+bool EnsureNotProcessingBuildConfig(const ParseNode* node,
+                                    const Scope* scope,
+                                    Err* err) {
+  if (scope->IsProcessingBuildConfig()) {
+    *err = Err(node, "Not valid from the build config.",
+               "You can't do this kind of thing from the build config script, "
+               "silly!\nPut it in a regular BUILD file.");
+    return false;
+  }
+  return true;
+}
+
+bool FillTargetBlockScope(const Scope* scope,
+                          const FunctionCallNode* function,
+                          const std::string& target_type,
+                          const BlockNode* block,
+                          const std::vector<Value>& args,
+                          Scope* block_scope,
+                          Err* err) {
+  if (!block) {
+    FillNeedsBlockError(function, err);
+    return false;
+  }
+
+  // Copy the target defaults, if any, into the scope we're going to execute
+  // the block in.
+  const Scope* default_scope = scope->GetTargetDefaults(target_type);
+  if (default_scope) {
+    Scope::MergeOptions merge_options;
+    merge_options.skip_private_vars = true;
+    if (!default_scope->NonRecursiveMergeTo(block_scope, merge_options,
+                                            function, "target defaults", err))
+      return false;
+  }
+
+  // The name is the single argument to the target function.
+  if (!EnsureSingleStringArg(function, args, err))
+    return false;
+
+  // Set the target name variable to the current target, and mark it used
+  // because we don't want to issue an error if the script ignores it.
+  const std::string_view target_name(variables::kTargetName);
+  block_scope->SetValue(target_name, Value(function, args[0].string_value()),
+                        function);
+  block_scope->MarkUsed(target_name);
+  return true;
+}
+
+void FillNeedsBlockError(const FunctionCallNode* function, Err* err) {
+  *err = Err(function->function(), "This function call requires a block.",
+             "The block's \"{\" must be on the same line as the function "
+             "call's \")\".");
+}
+
+bool EnsureSingleStringArg(const FunctionCallNode* function,
+                           const std::vector<Value>& args,
+                           Err* err) {
+  if (args.size() != 1) {
+    *err = Err(function->function(), "Incorrect arguments.",
+               "This function requires a single string argument.");
+    return false;
+  }
+  return args[0].VerifyTypeIs(Value::STRING, err);
+}
+
+const Label& ToolchainLabelForScope(const Scope* scope) {
+  return scope->settings()->toolchain_label();
+}
+
+Label MakeLabelForScope(const Scope* scope,
+                        const FunctionCallNode* function,
+                        const std::string& name) {
+  const Label& toolchain_label = ToolchainLabelForScope(scope);
+  return Label(scope->GetSourceDir(), name, toolchain_label.dir(),
+               toolchain_label.name());
+}
+
+// static
+const int NonNestableBlock::kKey = 0;
+
+NonNestableBlock::NonNestableBlock(Scope* scope,
+                                   const FunctionCallNode* function,
+                                   const char* type_description)
+    : scope_(scope),
+      function_(function),
+      type_description_(type_description),
+      key_added_(false) {}
+
+NonNestableBlock::~NonNestableBlock() {
+  if (key_added_)
+    scope_->SetProperty(&kKey, nullptr);
+}
+
+bool NonNestableBlock::Enter(Err* err) {
+  void* scope_value = scope_->GetProperty(&kKey, nullptr);
+  if (scope_value) {
+    // Existing block.
+    const NonNestableBlock* existing =
+        reinterpret_cast<const NonNestableBlock*>(scope_value);
+    *err = Err(function_, "Can't nest these things.",
+               std::string("You are trying to nest a ") + type_description_ +
+                   " inside a " + existing->type_description_ + ".");
+    err->AppendSubErr(Err(existing->function_, "The enclosing block."));
+    return false;
+  }
+
+  scope_->SetProperty(&kKey, this);
+  key_added_ = true;
+  return true;
+}
+
+namespace functions {
+
+// assert ----------------------------------------------------------------------
+
+const char kAssert[] = "assert";
+const char kAssert_HelpShort[] =
+    "assert: Assert an expression is true at generation time.";
+const char kAssert_Help[] =
+    R"(assert: Assert an expression is true at generation time.
+
+  assert(<condition> [, <error string>])
+
+  If the condition is false, the build will fail with an error. If the
+  optional second argument is provided, that string will be printed
+  with the error message.
+
+Examples
+
+  assert(is_win)
+  assert(defined(sources), "Sources must be defined");
+)";
+
+Value RunAssert(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err) {
+  // Check usage: Assert takes 1 or 2 arguments.
+  if (args.size() != 1 && args.size() != 2) {
+    *err = Err(function->function(), "Wrong number of arguments.",
+               "assert() takes one or two arguments, "
+               "were you expecting something else?");
+    return Value();
+  }
+
+  // Check usage: The first argument must be a boolean.
+  if (args[0].type() != Value::BOOLEAN) {
+    *err = Err(function->function(), "Assertion value not a bool.");
+    return Value();
+  }
+  bool assertion_passed = args[0].boolean_value();
+
+  // Check usage: The second argument, if present, must be a string.
+  if (args.size() == 2 && args[1].type() != Value::STRING) {
+    *err = Err(function->function(), "Assertion message is not a string.");
+    return Value();
+  }
+
+  // Assertion passed: there is nothing to do, so return an empty value.
+  if (assertion_passed) {
+    return Value();
+  }
+
+  // Assertion failed; try to make a useful message and report it.
+  if (args.size() == 2) {
+    *err =
+        Err(function->function(), "Assertion failed.", args[1].string_value());
+  } else {
+    *err = Err(function->function(), "Assertion failed.");
+  }
+  if (args[0].origin()) {
+    // If you do "assert(foo)" we'd ideally like to show you where foo was
+    // set, and in this case the origin of the args will tell us that.
+    // However, if you do "assert(foo && bar)" the source of the value will
+    // be the assert like, which isn't so helpful.
+    //
+    // So we try to see if the args are from the same line or not. This will
+    // break if you do "assert(\nfoo && bar)" and we may show the second line
+    // as the source, oh well. The way around this is to check to see if the
+    // origin node is inside our function call block.
+    Location origin_location = args[0].origin()->GetRange().begin();
+    if (origin_location.file() != function->function().location().file() ||
+        origin_location.line_number() !=
+            function->function().location().line_number()) {
+      err->AppendSubErr(
+          Err(args[0].origin()->GetRange(), "", "This is where it was set."));
+    }
+  }
+  return Value();
+}
+
+// config ----------------------------------------------------------------------
+
+const char kConfig[] = "config";
+const char kConfig_HelpShort[] = "config: Defines a configuration object.";
+const char kConfig_Help[] =
+    R"(config: Defines a configuration object.
+
+  Configuration objects can be applied to targets and specify sets of compiler
+  flags, includes, defines, etc. They provide a way to conveniently group sets
+  of this configuration information.
+
+  A config is referenced by its label just like a target.
+
+  The values in a config are additive only. If you want to remove a flag you
+  need to remove the corresponding config that sets it. The final set of flags,
+  defines, etc. for a target is generated in this order:
+
+   1. The values specified directly on the target (rather than using a config.
+   2. The configs specified in the target's "configs" list, in order.
+   3. Public_configs from a breadth-first traversal of the dependency tree in
+      the order that the targets appear in "deps".
+   4. All dependent configs from a breadth-first traversal of the dependency
+      tree in the order that the targets appear in "deps".
+
+More background
+
+  Configs solve a problem where the build system needs to have a higher-level
+  understanding of various compiler settings. For example, some compiler flags
+  have to appear in a certain order relative to each other, some settings like
+  defines and flags logically go together, and the build system needs to
+  de-duplicate flags even though raw command-line parameters can't always be
+  operated on in that way.
+
+  The config gives a name to a group of settings that can then be reasoned
+  about by GN. GN can know that configs with the same label are the same thing
+  so can be de-duplicated. It allows related settings to be grouped so they
+  are added or removed as a unit. And it allows targets to refer to settings
+  with conceptual names ("no_rtti", "enable_exceptions", etc.) rather than
+  having to hard-coding every compiler's flags each time they are referred to.
+
+Variables valid in a config definition
+
+)"
+
+    CONFIG_VALUES_VARS_HELP
+
+    R"(  Nested configs: configs
+
+Variables on a target used to apply configs
+
+  all_dependent_configs, configs, public_configs
+
+Example
+
+  config("myconfig") {
+    include_dirs = [ "include/common" ]
+    defines = [ "ENABLE_DOOM_MELON" ]
+  }
+
+  executable("mything") {
+    configs = [ ":myconfig" ]
+  }
+)";
+
+Value RunConfig(const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Scope* scope,
+                Err* err) {
+  NonNestableBlock non_nestable(scope, function, "config");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  if (!EnsureSingleStringArg(function, args, err) ||
+      !EnsureNotProcessingImport(function, scope, err))
+    return Value();
+
+  Label label(MakeLabelForScope(scope, function, args[0].string_value()));
+
+  if (g_scheduler->verbose_logging())
+    g_scheduler->Log("Defining config", label.GetUserVisibleName(true));
+
+  // Create the new config.
+  std::unique_ptr<Config> config = std::make_unique<Config>(
+      scope->settings(), label, scope->build_dependency_files());
+  config->set_defined_from(function);
+  if (!Visibility::FillItemVisibility(config.get(), scope, err))
+    return Value();
+
+  // Fill the flags and such.
+  const SourceDir& input_dir = scope->GetSourceDir();
+  ConfigValuesGenerator gen(&config->own_values(), scope, input_dir, err);
+  gen.Run();
+  if (err->has_error())
+    return Value();
+
+  // Read sub-configs.
+  const Value* configs_value = scope->GetValue(variables::kConfigs, true);
+  if (configs_value) {
+    ExtractListOfUniqueLabels(scope->settings()->build_settings(),
+                              *configs_value, scope->GetSourceDir(),
+                              ToolchainLabelForScope(scope), &config->configs(),
+                              err);
+  }
+  if (err->has_error())
+    return Value();
+
+  // Save the generated item.
+  Scope::ItemVector* collector = scope->GetItemCollector();
+  if (!collector) {
+    *err = Err(function, "Can't define a config in this context.");
+    return Value();
+  }
+  collector->push_back(std::move(config));
+
+  return Value();
+}
+
+// declare_args ----------------------------------------------------------------
+
+const char kDeclareArgs[] = "declare_args";
+const char kDeclareArgs_HelpShort[] = "declare_args: Declare build arguments.";
+const char kDeclareArgs_Help[] =
+    R"(declare_args: Declare build arguments.
+
+  Introduces the given arguments into the current scope. If they are not
+  specified on the command line or in a toolchain's arguments, the default
+  values given in the declare_args block will be used. However, these defaults
+  will not override command-line values.
+
+  See also "gn help buildargs" for an overview.
+
+  The precise behavior of declare args is:
+
+   1. The declare_args() block executes. Any variable defined in the enclosing
+      scope is available for reading, but any variable defined earlier in
+      the current scope is not (since the overrides haven't been applied yet).
+
+   2. At the end of executing the block, any variables set within that scope
+      are saved, with the values specified in the block used as the "default value"
+      for that argument. Once saved, these variables are available for override
+      via args.gn.
+
+   3. User-defined overrides are applied. Anything set in "gn args" now
+      overrides any default values. The resulting set of variables is promoted
+      to be readable from the following code in the file.
+
+  This has some ramifications that may not be obvious:
+
+    - You should not perform difficult work inside a declare_args block since
+      this only sets a default value that may be discarded. In particular,
+      don't use the result of exec_script() to set the default value. If you
+      want to have a script-defined default, set some default "undefined" value
+      like [], "", or -1, and after the declare_args block, call exec_script if
+      the value is unset by the user.
+
+    - Because you cannot read the value of a variable defined in the same
+      block, if you need to make the default value of one arg depend
+      on the possibly-overridden value of another, write two separate
+      declare_args() blocks:
+
+        declare_args() {
+          enable_foo = true
+        }
+        declare_args() {
+          # Bar defaults to same user-overridden state as foo.
+          enable_bar = enable_foo
+        }
+
+Example
+
+  declare_args() {
+    enable_teleporter = true
+    enable_doom_melon = false
+  }
+
+  If you want to override the (default disabled) Doom Melon:
+    gn --args="enable_doom_melon=true enable_teleporter=true"
+  This also sets the teleporter, but it's already defaulted to on so it will
+  have no effect.
+)";
+
+Value RunDeclareArgs(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err) {
+  NonNestableBlock non_nestable(scope, function, "declare_args");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  Scope block_scope(scope);
+  block_scope.SetProperty(&kInDeclareArgsKey, &block_scope);
+  block->Execute(&block_scope, err);
+  if (err->has_error())
+    return Value();
+
+  // Pass the values from our scope into the Args object for adding to the
+  // scope with the proper values (taking into account the defaults given in
+  // the block_scope, and arguments passed into the build).
+  Scope::KeyValueMap values;
+  block_scope.GetCurrentScopeValues(&values);
+  scope->settings()->build_settings()->build_args().DeclareArgs(values, scope,
+                                                                err);
+  return Value();
+}
+
+// defined ---------------------------------------------------------------------
+
+const char kDefined[] = "defined";
+const char kDefined_HelpShort[] =
+    "defined: Returns whether an identifier is defined.";
+const char kDefined_Help[] =
+    R"(defined: Returns whether an identifier is defined.
+
+  Returns true if the given argument is defined. This is most useful in
+  templates to assert that the caller set things up properly.
+
+  You can pass an identifier:
+    defined(foo)
+  which will return true or false depending on whether foo is defined in the
+  current scope.
+
+  You can also check a named scope:
+    defined(foo.bar)
+  which will return true or false depending on whether bar is defined in the
+  named scope foo. It will throw an error if foo is not defined or is not a
+  scope.
+
+Example
+
+  template("mytemplate") {
+    # To help users call this template properly...
+    assert(defined(invoker.sources), "Sources must be defined")
+
+    # If we want to accept an optional "values" argument, we don't
+    # want to dereference something that may not be defined.
+    if (defined(invoker.values)) {
+      values = invoker.values
+    } else {
+      values = "some default value"
+    }
+  }
+)";
+
+Value RunDefined(Scope* scope,
+                 const FunctionCallNode* function,
+                 const ListNode* args_list,
+                 Err* err) {
+  const auto& args_vector = args_list->contents();
+  if (args_vector.size() != 1) {
+    *err = Err(function, "Wrong number of arguments to defined().",
+               "Expecting exactly one.");
+    return Value();
+  }
+
+  const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
+  if (identifier) {
+    // Passed an identifier "defined(foo)".
+    if (scope->GetValue(identifier->value().value()))
+      return Value(function, true);
+    return Value(function, false);
+  }
+
+  const AccessorNode* accessor = args_vector[0]->AsAccessor();
+  if (accessor) {
+    // Passed an accessor "defined(foo.bar)".
+    if (accessor->member()) {
+      // The base of the accessor must be a scope if it's defined.
+      const Value* base = scope->GetValue(accessor->base().value());
+      if (!base) {
+        *err = Err(accessor, "Undefined identifier");
+        return Value();
+      }
+      if (!base->VerifyTypeIs(Value::SCOPE, err))
+        return Value();
+
+      // Check the member inside the scope to see if its defined.
+      if (base->scope_value()->GetValue(accessor->member()->value().value()))
+        return Value(function, true);
+      return Value(function, false);
+    }
+  }
+
+  // Argument is invalid.
+  *err = Err(function, "Bad thing passed to defined().",
+             "It should be of the form defined(foo) or defined(foo.bar).");
+  return Value();
+}
+
+// getenv ----------------------------------------------------------------------
+
+const char kGetEnv[] = "getenv";
+const char kGetEnv_HelpShort[] = "getenv: Get an environment variable.";
+const char kGetEnv_Help[] =
+    R"(getenv: Get an environment variable.
+
+  value = getenv(env_var_name)
+
+  Returns the value of the given environment variable. If the value is not
+  found, it will try to look up the variable with the "opposite" case (based on
+  the case of the first letter of the variable), but is otherwise
+  case-sensitive.
+
+  If the environment variable is not found, the empty string will be returned.
+  Note: it might be nice to extend this if we had the concept of "none" in the
+  language to indicate lookup failure.
+
+Example
+
+  home_dir = getenv("HOME")
+)";
+
+Value RunGetEnv(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err) {
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+
+  std::string result;
+  if (!env->GetVar(args[0].string_value().c_str(), &result))
+    return Value(function, "");  // Not found, return empty string.
+  return Value(function, result);
+}
+
+// import ----------------------------------------------------------------------
+
+const char kImport[] = "import";
+const char kImport_HelpShort[] =
+    "import: Import a file into the current scope.";
+const char kImport_Help[] =
+    R"(import: Import a file into the current scope.
+
+  The import command loads the rules and variables resulting from executing the
+  given file into the current scope.
+
+  By convention, imported files are named with a .gni extension.
+
+  An import is different than a C++ "include". The imported file is executed in
+  a standalone environment from the caller of the import command. The results
+  of this execution are cached for other files that import the same .gni file.
+
+  Note that you can not import a BUILD.gn file that's otherwise used in the
+  build. Files must either be imported or implicitly loaded as a result of deps
+  rules, but not both.
+
+  The imported file's scope will be merged with the scope at the point import
+  was called. If there is a conflict (both the current scope and the imported
+  file define some variable or rule with the same name but different value), a
+  runtime error will be thrown. Therefore, it's good practice to minimize the
+  stuff that an imported file defines.
+
+  Variables and templates beginning with an underscore '_' are considered
+  private and will not be imported. Imported files can use such variables for
+  internal computation without affecting other files.
+
+Examples
+
+  import("//build/rules/idl_compilation_rule.gni")
+
+  # Looks in the current directory.
+  import("my_vars.gni")
+)";
+
+Value RunImport(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err) {
+  if (!EnsureSingleStringArg(function, args, err))
+    return Value();
+
+  const SourceDir& input_dir = scope->GetSourceDir();
+  SourceFile import_file = input_dir.ResolveRelativeFile(
+      args[0], err, scope->settings()->build_settings()->root_path_utf8());
+  scope->AddBuildDependencyFile(import_file);
+  if (!err->has_error()) {
+    scope->settings()->import_manager().DoImport(import_file, function, scope,
+                                                 err);
+  }
+  return Value();
+}
+
+// not_needed -----------------------------------------------------------------
+
+const char kNotNeeded[] = "not_needed";
+const char kNotNeeded_HelpShort[] =
+    "not_needed: Mark variables from scope as not needed.";
+const char kNotNeeded_Help[] =
+    R"(not_needed: Mark variables from scope as not needed.
+
+  not_needed(variable_list_or_star, variable_to_ignore_list = [])
+  not_needed(from_scope, variable_list_or_star,
+             variable_to_ignore_list = [])
+
+  Mark the variables in the current or given scope as not needed, which means
+  you will not get an error about unused variables for these. The
+  variable_to_ignore_list allows excluding variables from "all matches" if
+  variable_list_or_star is "*".
+
+Example
+
+  not_needed("*", [ "config" ])
+  not_needed([ "data_deps", "deps" ])
+  not_needed(invoker, "*", [ "config" ])
+  not_needed(invoker, [ "data_deps", "deps" ])
+)";
+
+Value RunNotNeeded(Scope* scope,
+                   const FunctionCallNode* function,
+                   const ListNode* args_list,
+                   Err* err) {
+  const auto& args_vector = args_list->contents();
+  if (args_vector.size() < 1 || args_vector.size() > 3) {
+    *err = Err(function, "Wrong number of arguments.",
+               "Expecting one, two or three arguments.");
+    return Value();
+  }
+  auto args_cur = args_vector.begin();
+
+  Value* value = nullptr;  // Value to use, may point to result_value.
+  Value result_value;      // Storage for the "evaluate" case.
+  Value scope_value;       // Storage for an evaluated scope.
+  const IdentifierNode* identifier = (*args_cur)->AsIdentifier();
+  if (identifier) {
+    // Optimize the common case where the input scope is an identifier. This
+    // prevents a copy of a potentially large Scope object.
+    value = scope->GetMutableValue(identifier->value().value(),
+                                   Scope::SEARCH_NESTED, true);
+    if (!value) {
+      *err = Err(identifier, "Undefined identifier.");
+      return Value();
+    }
+  } else {
+    // Non-optimized case, just evaluate the argument.
+    result_value = (*args_cur)->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+    value = &result_value;
+  }
+  args_cur++;
+
+  // Extract the source scope if different from current one.
+  Scope* source = scope;
+  if (value->type() == Value::SCOPE) {
+    if (args_cur == args_vector.end()) {
+      *err = Err(
+          function, "Wrong number of arguments.",
+          "The first argument is a scope, expecting two or three arguments.");
+      return Value();
+    }
+    // Copy the scope value if it will be overridden.
+    if (value == &result_value) {
+      scope_value = Value(nullptr, value->scope_value()->MakeClosure());
+      source = scope_value.scope_value();
+    } else {
+      source = value->scope_value();
+    }
+    result_value = (*args_cur)->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+    value = &result_value;
+    args_cur++;
+  } else if (args_vector.size() > 2) {
+    *err = Err(
+        function, "Wrong number of arguments.",
+        "The first argument is not a scope, expecting one or two arguments.");
+    return Value();
+  }
+
+  // Extract the exclusion list if defined.
+  Value exclusion_value;
+  std::set<std::string> exclusion_set;
+  if (args_cur != args_vector.end()) {
+    exclusion_value = (*args_cur)->Execute(source, err);
+    if (err->has_error())
+      return Value();
+
+    if (exclusion_value.type() != Value::LIST) {
+      *err = Err(exclusion_value, "Not a valid list of variables to exclude.",
+                 "Expecting a list of strings.");
+      return Value();
+    }
+
+    for (const Value& cur : exclusion_value.list_value()) {
+      if (!cur.VerifyTypeIs(Value::STRING, err))
+        return Value();
+
+      exclusion_set.insert(cur.string_value());
+    }
+  }
+
+  if (value->type() == Value::STRING) {
+    if (value->string_value() == "*") {
+      source->MarkAllUsed(exclusion_set);
+      return Value();
+    }
+  } else if (value->type() == Value::LIST) {
+    if (exclusion_value.type() != Value::NONE) {
+      *err = Err(exclusion_value, "Not supported with a variable list.",
+                 "Exclusion list can only be used with the string \"*\".");
+      return Value();
+    }
+    for (const Value& cur : value->list_value()) {
+      if (!cur.VerifyTypeIs(Value::STRING, err))
+        return Value();
+      // We don't need the return value, we invoke scope::GetValue only to mark
+      // the value as used. Note that we cannot use Scope::MarkUsed because we
+      // want to also search in the parent scope.
+      (void)source->GetValue(cur.string_value(), true);
+    }
+    return Value();
+  }
+
+  // Not the right type of argument.
+  *err = Err(*value, "Not a valid list of variables.",
+             "Expecting either the string \"*\" or a list of strings.");
+  return Value();
+}
+
+// pool ------------------------------------------------------------------------
+
+const char kPool[] = "pool";
+const char kPool_HelpShort[] = "pool: Defines a pool object.";
+const char kPool_Help[] =
+    R"*(pool: Defines a pool object.
+
+  Pool objects can be applied to a tool to limit the parallelism of the
+  build. This object has a single property "depth" corresponding to
+  the number of tasks that may run simultaneously.
+
+  As the file containing the pool definition may be executed in the
+  context of more than one toolchain it is recommended to specify an
+  explicit toolchain when defining and referencing a pool.
+
+  A pool named "console" defined in the root build file represents Ninja's
+  console pool. Targets using this pool will have access to the console's
+  stdin and stdout, and output will not be buffered. This special pool must
+  have a depth of 1. Pools not defined in the root must not be named "console".
+  The console pool can only be defined for the default toolchain.
+  Refer to the Ninja documentation on the console pool for more info.
+
+  A pool is referenced by its label just like a target.
+
+Variables
+
+  depth*
+  * = required
+
+Example
+
+  if (current_toolchain == default_toolchain) {
+    pool("link_pool") {
+      depth = 1
+    }
+  }
+
+  toolchain("toolchain") {
+    tool("link") {
+      command = "..."
+      pool = ":link_pool($default_toolchain)"
+    }
+  }
+)*";
+
+const char kDepth[] = "depth";
+
+Value RunPool(const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              Scope* scope,
+              Err* err) {
+  NonNestableBlock non_nestable(scope, function, "pool");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  if (!EnsureSingleStringArg(function, args, err) ||
+      !EnsureNotProcessingImport(function, scope, err))
+    return Value();
+
+  Label label(MakeLabelForScope(scope, function, args[0].string_value()));
+
+  if (g_scheduler->verbose_logging())
+    g_scheduler->Log("Defining pool", label.GetUserVisibleName(true));
+
+  // Get the pool depth. It is an error to define a pool without a depth,
+  // so check first for the presence of the value.
+  const Value* depth = scope->GetValue(kDepth, true);
+  if (!depth) {
+    *err = Err(function, "Can't define a pool without depth.");
+    return Value();
+  }
+
+  if (!depth->VerifyTypeIs(Value::INTEGER, err))
+    return Value();
+
+  if (depth->int_value() < 0) {
+    *err = Err(*depth, "depth must be positive or 0.");
+    return Value();
+  }
+
+  // Create the new pool.
+  std::unique_ptr<Pool> pool = std::make_unique<Pool>(
+      scope->settings(), label, scope->build_dependency_files());
+
+  if (label.name() == "console") {
+    const Settings* settings = scope->settings();
+    if (!settings->is_default()) {
+      *err = Err(
+          function,
+          "\"console\" pool must be defined only in the default toolchain.");
+      return Value();
+    }
+    if (label.dir() != settings->build_settings()->root_target_label().dir()) {
+      *err = Err(function, "\"console\" pool must be defined in the root //.");
+      return Value();
+    }
+    if (depth->int_value() != 1) {
+      *err = Err(*depth, "\"console\" pool must have depth 1.");
+      return Value();
+    }
+  }
+  pool->set_depth(depth->int_value());
+
+  // Save the generated item.
+  Scope::ItemVector* collector = scope->GetItemCollector();
+  if (!collector) {
+    *err = Err(function, "Can't define a pool in this context.");
+    return Value();
+  }
+  collector->push_back(std::move(pool));
+
+  return Value();
+}
+
+// print -----------------------------------------------------------------------
+
+const char kPrint[] = "print";
+const char kPrint_HelpShort[] = "print: Prints to the console.";
+const char kPrint_Help[] =
+    R"(print: Prints to the console.
+
+  Prints all arguments to the console separated by spaces. A newline is
+  automatically appended to the end.
+
+  This function is intended for debugging. Note that build files are run in
+  parallel so you may get interleaved prints. A buildfile may also be executed
+  more than once in parallel in the context of different toolchains so the
+  prints from one file may be duplicated or
+  interleaved with itself.
+
+Examples
+
+  print("Hello world")
+
+  print(sources, deps)
+)";
+
+Value RunPrint(Scope* scope,
+               const FunctionCallNode* function,
+               const std::vector<Value>& args,
+               Err* err) {
+  std::string output;
+  for (size_t i = 0; i < args.size(); i++) {
+    if (i != 0)
+      output.push_back(' ');
+    output.append(args[i].ToString(false));
+  }
+  output.push_back('\n');
+
+  const BuildSettings::PrintCallback& cb =
+      scope->settings()->build_settings()->print_callback();
+  if (cb) {
+    cb(output);
+  } else {
+    printf("%s", output.c_str());
+    fflush(stdout);
+  }
+
+  return Value();
+}
+
+// split_list ------------------------------------------------------------------
+
+const char kSplitList[] = "split_list";
+const char kSplitList_HelpShort[] =
+    "split_list: Splits a list into N different sub-lists.";
+const char kSplitList_Help[] =
+    R"(split_list: Splits a list into N different sub-lists.
+
+  result = split_list(input, n)
+
+  Given a list and a number N, splits the list into N sub-lists of
+  approximately equal size. The return value is a list of the sub-lists. The
+  result will always be a list of size N. If N is greater than the number of
+  elements in the input, it will be padded with empty lists.
+
+  The expected use is to divide source files into smaller uniform chunks.
+
+Example
+
+  The code:
+    mylist = [1, 2, 3, 4, 5, 6]
+    print(split_list(mylist, 3))
+
+  Will print:
+    [[1, 2], [3, 4], [5, 6]
+)";
+Value RunSplitList(Scope* scope,
+                   const FunctionCallNode* function,
+                   const ListNode* args_list,
+                   Err* err) {
+  const auto& args_vector = args_list->contents();
+  if (args_vector.size() != 2) {
+    *err = Err(function, "Wrong number of arguments to split_list().",
+               "Expecting exactly two.");
+    return Value();
+  }
+
+  ParseNodeValueAdapter list_adapter;
+  if (!list_adapter.InitForType(scope, args_vector[0].get(), Value::LIST, err))
+    return Value();
+  const std::vector<Value>& input = list_adapter.get().list_value();
+
+  ParseNodeValueAdapter count_adapter;
+  if (!count_adapter.InitForType(scope, args_vector[1].get(), Value::INTEGER,
+                                 err))
+    return Value();
+  int64_t count = count_adapter.get().int_value();
+  if (count <= 0) {
+    *err = Err(function, "Requested result size is not positive.");
+    return Value();
+  }
+
+  Value result(function, Value::LIST);
+  result.list_value().resize(count);
+
+  // Every result list gets at least this many items in it.
+  int64_t min_items_per_list = static_cast<int64_t>(input.size()) / count;
+
+  // This many result lists get an extra item which is the remainder from above.
+  int64_t extra_items = static_cast<int64_t>(input.size()) % count;
+
+  // Allocate all lists that have a remainder assigned to them (max items).
+  int64_t max_items_per_list = min_items_per_list + 1;
+  auto last_item_end = input.begin();
+  for (int64_t i = 0; i < extra_items; i++) {
+    result.list_value()[i] = Value(function, Value::LIST);
+
+    auto begin_add = last_item_end;
+    last_item_end += max_items_per_list;
+    result.list_value()[i].list_value().assign(begin_add, last_item_end);
+  }
+
+  // Allocate all smaller items that don't have a remainder.
+  for (int64_t i = extra_items; i < count; i++) {
+    result.list_value()[i] = Value(function, Value::LIST);
+
+    auto begin_add = last_item_end;
+    last_item_end += min_items_per_list;
+    result.list_value()[i].list_value().assign(begin_add, last_item_end);
+  }
+
+  return result;
+}
+
+// string_join -----------------------------------------------------------------
+
+const char kStringJoin[] = "string_join";
+const char kStringJoin_HelpShort[] =
+    "string_join: Concatenates a list of strings with a separator.";
+const char kStringJoin_Help[] =
+    R"(string_join: Concatenates a list of strings with a separator.
+
+  result = string_join(separator, strings)
+
+  Concatenate a list of strings with intervening occurrences of separator.
+
+Examples
+
+    string_join("", ["a", "b", "c"])    --> "abc"
+    string_join("|", ["a", "b", "c"])   --> "a|b|c"
+    string_join(", ", ["a", "b", "c"])  --> "a, b, c"
+    string_join("s", ["", ""])          --> "s"
+)";
+
+Value RunStringJoin(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    Err* err) {
+  // Check usage: Number of arguments.
+  if (args.size() != 2) {
+    *err = Err(function, "Wrong number of arguments to string_join().",
+               "Expecting exactly two. usage: string_join(separator, strings)");
+    return Value();
+  }
+
+  // Check usage: separator is a string.
+  if (!args[0].VerifyTypeIs(Value::STRING, err)) {
+    *err = Err(function,
+               "separator in string_join(separator, strings) is not "
+               "a string",
+               "Expecting separator argument to be a string.");
+    return Value();
+  }
+  const std::string separator = args[0].string_value();
+
+  // Check usage: strings is a list.
+  if (!args[1].VerifyTypeIs(Value::LIST, err)) {
+    *err = Err(function,
+               "strings in string_join(separator, strings) "
+               "is not a list",
+               "Expecting strings argument to be a list.");
+    return Value();
+  }
+  const std::vector<Value> strings = args[1].list_value();
+
+  // Arguments looks good; do the join.
+  std::stringstream stream;
+  for (size_t i = 0; i < strings.size(); ++i) {
+    if (!strings[i].VerifyTypeIs(Value::STRING, err)) {
+      return Value();
+    }
+    if (i != 0) {
+      stream << separator;
+    }
+    stream << strings[i].string_value();
+  }
+  return Value(function, stream.str());
+}
+
+// string_replace --------------------------------------------------------------
+
+const char kStringReplace[] = "string_replace";
+const char kStringReplace_HelpShort[] =
+    "string_replace: Replaces substring in the given string.";
+const char kStringReplace_Help[] =
+    R"(string_replace: Replaces substring in the given string.
+
+  result = string_replace(str, old, new[, max])
+
+  Returns a copy of the string str in which the occurrences of old have been
+  replaced with new, optionally restricting the number of replacements. The
+  replacement is performed sequentially, so if new contains old, it won't be
+  replaced.
+
+Example
+
+  The code:
+    mystr = "Hello, world!"
+    print(string_replace(mystr, "world", "GN"))
+
+  Will print:
+    Hello, GN!
+)";
+
+Value RunStringReplace(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err) {
+  if (args.size() < 3 || args.size() > 4) {
+    *err = Err(function, "Wrong number of arguments to string_replace().");
+    return Value();
+  }
+
+  if (!args[0].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const std::string str = args[0].string_value();
+
+  if (!args[1].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const std::string& old = args[1].string_value();
+
+  if (!args[2].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const std::string& new_ = args[2].string_value();
+
+  int64_t max = INT64_MAX;
+  if (args.size() > 3) {
+    if (!args[3].VerifyTypeIs(Value::INTEGER, err))
+      return Value();
+    max = args[3].int_value();
+    if (max <= 0) {
+      *err = Err(function, "Requested number of replacements is not positive.");
+      return Value();
+    }
+  }
+
+  int64_t n = 0;
+  std::string val(str);
+  size_t start_pos = 0;
+  while ((start_pos = val.find(old, start_pos)) != std::string::npos) {
+    val.replace(start_pos, old.length(), new_);
+    start_pos += new_.length();
+    if (++n >= max)
+      break;
+  }
+  return Value(function, std::move(val));
+}
+
+// string_split ----------------------------------------------------------------
+
+const char kStringSplit[] = "string_split";
+const char kStringSplit_HelpShort[] =
+    "string_split: Split string into a list of strings.";
+const char kStringSplit_Help[] =
+    R"(string_split: Split string into a list of strings.
+
+  result = string_split(str[, sep])
+
+  Split string into all substrings separated by separator and returns a list
+  of the substrings between those separators.
+
+  If the separator argument is omitted, the split is by any whitespace, and
+  any leading/trailing whitespace is ignored; similar to Python's str.split().
+
+Examples without a separator (split on whitespace):
+
+  string_split("")          --> []
+  string_split("a")         --> ["a"]
+  string_split(" aa  bb")   --> ["aa", "bb"]
+
+Examples with a separator (split on separators):
+
+  string_split("", "|")           --> [""]
+  string_split("  a b  ", " ")    --> ["", "", "a", "b", "", ""]
+  string_split("aa+-bb+-c", "+-") --> ["aa", "bb", "c"]
+)";
+
+Value RunStringSplit(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     Err* err) {
+  // Check usage: argument count.
+  if (args.size() != 1 && args.size() != 2) {
+    *err = Err(function, "Wrong number of arguments to string_split().",
+               "Usage: string_split(str[, sep])");
+    return Value();
+  }
+
+  // Check usage: str is a string.
+  if (!args[0].VerifyTypeIs(Value::STRING, err)) {
+    return Value();
+  }
+  const std::string str = args[0].string_value();
+
+  // Check usage: separator is a non-empty string.
+  std::string separator;
+  if (args.size() == 2) {
+    if (!args[1].VerifyTypeIs(Value::STRING, err)) {
+      return Value();
+    }
+    separator = args[1].string_value();
+    if (separator.empty()) {
+      *err = Err(function,
+                 "Separator argument to string_split() "
+                 "cannot be empty string",
+                 "Usage: string_split(str[, sep])");
+      return Value();
+    }
+  }
+
+  // Split the string into a std::vector.
+  std::vector<std::string> strings;
+  if (!separator.empty()) {
+    // Case: Explicit separator argument.
+    // Note: split_string("", "x") --> [""] like Python.
+    size_t pos = 0;
+    size_t next_pos = 0;
+    while ((next_pos = str.find(separator, pos)) != std::string::npos) {
+      strings.push_back(str.substr(pos, next_pos - pos));
+      pos = next_pos + separator.length();
+    }
+    strings.push_back(str.substr(pos, std::string::npos));
+  } else {
+    // Case: Split on any whitespace and strip ends.
+    // Note: split_string("") --> [] like Python.
+    std::string::const_iterator pos = str.cbegin();
+    while (pos != str.end()) {
+      // Advance past spaces. After this, pos is pointing to non-whitespace.
+      pos = find_if(pos, str.end(), [](char x) { return !std::isspace(x); });
+      if (pos == str.end()) {
+        // Tail is all whitespace, so we're done.
+        break;
+      }
+      // Advance past non-whitespace to get next chunk.
+      std::string::const_iterator next_whitespace_position =
+          find_if(pos, str.end(), [](char x) { return std::isspace(x); });
+      strings.push_back(std::string(pos, next_whitespace_position));
+      pos = next_whitespace_position;
+    }
+  }
+
+  // Convert vector of std::strings to list of GN strings.
+  Value result(function, Value::LIST);
+  result.list_value().resize(strings.size());
+  for (size_t i = 0; i < strings.size(); ++i) {
+    result.list_value()[i] = Value(function, strings[i]);
+  }
+  return result;
+}
+
+// -----------------------------------------------------------------------------
+
+FunctionInfo::FunctionInfo()
+    : self_evaluating_args_runner(nullptr),
+      generic_block_runner(nullptr),
+      executed_block_runner(nullptr),
+      no_block_runner(nullptr),
+      help_short(nullptr),
+      help(nullptr),
+      is_target(false) {}
+
+FunctionInfo::FunctionInfo(SelfEvaluatingArgsFunction seaf,
+                           const char* in_help_short,
+                           const char* in_help,
+                           bool in_is_target)
+    : self_evaluating_args_runner(seaf),
+      generic_block_runner(nullptr),
+      executed_block_runner(nullptr),
+      no_block_runner(nullptr),
+      help_short(in_help_short),
+      help(in_help),
+      is_target(in_is_target) {}
+
+FunctionInfo::FunctionInfo(GenericBlockFunction gbf,
+                           const char* in_help_short,
+                           const char* in_help,
+                           bool in_is_target)
+    : self_evaluating_args_runner(nullptr),
+      generic_block_runner(gbf),
+      executed_block_runner(nullptr),
+      no_block_runner(nullptr),
+      help_short(in_help_short),
+      help(in_help),
+      is_target(in_is_target) {}
+
+FunctionInfo::FunctionInfo(ExecutedBlockFunction ebf,
+                           const char* in_help_short,
+                           const char* in_help,
+                           bool in_is_target)
+    : self_evaluating_args_runner(nullptr),
+      generic_block_runner(nullptr),
+      executed_block_runner(ebf),
+      no_block_runner(nullptr),
+      help_short(in_help_short),
+      help(in_help),
+      is_target(in_is_target) {}
+
+FunctionInfo::FunctionInfo(NoBlockFunction nbf,
+                           const char* in_help_short,
+                           const char* in_help,
+                           bool in_is_target)
+    : self_evaluating_args_runner(nullptr),
+      generic_block_runner(nullptr),
+      executed_block_runner(nullptr),
+      no_block_runner(nbf),
+      help_short(in_help_short),
+      help(in_help),
+      is_target(in_is_target) {}
+
+// Setup the function map via a static initializer. We use this because it
+// avoids race conditions without having to do some global setup function or
+// locking-heavy singleton checks at runtime. In practice, we always need this
+// before we can do anything interesting, so it's OK to wait for the
+// initializer.
+struct FunctionInfoInitializer {
+  FunctionInfoMap map;
+
+  FunctionInfoInitializer() {
+#define INSERT_FUNCTION(command, is_target)                             \
+  map[k##command] = FunctionInfo(&Run##command, k##command##_HelpShort, \
+                                 k##command##_Help, is_target);
+
+    INSERT_FUNCTION(Action, true)
+    INSERT_FUNCTION(ActionForEach, true)
+    INSERT_FUNCTION(BundleData, true)
+    INSERT_FUNCTION(CreateBundle, true)
+    INSERT_FUNCTION(Copy, true)
+    INSERT_FUNCTION(Executable, true)
+    INSERT_FUNCTION(Group, true)
+    INSERT_FUNCTION(LoadableModule, true)
+    INSERT_FUNCTION(SharedLibrary, true)
+    INSERT_FUNCTION(SourceSet, true)
+    INSERT_FUNCTION(StaticLibrary, true)
+    INSERT_FUNCTION(Target, true)
+    INSERT_FUNCTION(GeneratedFile, true)
+    INSERT_FUNCTION(RustLibrary, true)
+    INSERT_FUNCTION(RustProcMacro, true)
+
+    INSERT_FUNCTION(Assert, false)
+    INSERT_FUNCTION(Config, false)
+    INSERT_FUNCTION(DeclareArgs, false)
+    INSERT_FUNCTION(Defined, false)
+    INSERT_FUNCTION(ExecScript, false)
+    INSERT_FUNCTION(FilterExclude, false)
+    INSERT_FUNCTION(FilterInclude, false)
+    INSERT_FUNCTION(ForEach, false)
+    INSERT_FUNCTION(ForwardVariablesFrom, false)
+    INSERT_FUNCTION(GetEnv, false)
+    INSERT_FUNCTION(GetLabelInfo, false)
+    INSERT_FUNCTION(GetPathInfo, false)
+    INSERT_FUNCTION(GetTargetOutputs, false)
+    INSERT_FUNCTION(Import, false)
+    INSERT_FUNCTION(NotNeeded, false)
+    INSERT_FUNCTION(Pool, false)
+    INSERT_FUNCTION(Print, false)
+    INSERT_FUNCTION(ProcessFileTemplate, false)
+    INSERT_FUNCTION(ReadFile, false)
+    INSERT_FUNCTION(RebasePath, false)
+    INSERT_FUNCTION(SetDefaults, false)
+    INSERT_FUNCTION(SetDefaultToolchain, false)
+    INSERT_FUNCTION(SplitList, false)
+    INSERT_FUNCTION(StringJoin, false)
+    INSERT_FUNCTION(StringReplace, false)
+    INSERT_FUNCTION(StringSplit, false)
+    INSERT_FUNCTION(Template, false)
+    INSERT_FUNCTION(Tool, false)
+    INSERT_FUNCTION(Toolchain, false)
+    INSERT_FUNCTION(WriteFile, false)
+
+#undef INSERT_FUNCTION
+  }
+};
+const FunctionInfoInitializer function_info;
+
+const FunctionInfoMap& GetFunctions() {
+  return function_info.map;
+}
+
+Value RunFunction(Scope* scope,
+                  const FunctionCallNode* function,
+                  const ListNode* args_list,
+                  BlockNode* block,
+                  Err* err) {
+  const Token& name = function->function();
+
+  std::string template_name(function->function().value());
+  const Template* templ = scope->GetTemplate(template_name);
+  if (templ) {
+    Value args = args_list->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+    return templ->Invoke(scope, function, template_name, args.list_value(),
+                         block, err);
+  }
+
+  // No template matching this, check for a built-in function.
+  const FunctionInfoMap& function_map = GetFunctions();
+  FunctionInfoMap::const_iterator found_function =
+      function_map.find(name.value());
+  if (found_function == function_map.end()) {
+    *err = Err(name, "Unknown function.");
+    return Value();
+  }
+
+  if (found_function->second.self_evaluating_args_runner) {
+    // Self evaluating args functions are special weird built-ins like foreach.
+    // Rather than force them all to check that they have a block or no block
+    // and risk bugs for new additions, check a whitelist here.
+    if (found_function->second.self_evaluating_args_runner != &RunForEach) {
+      if (!VerifyNoBlockForFunctionCall(function, block, err))
+        return Value();
+    }
+    return found_function->second.self_evaluating_args_runner(scope, function,
+                                                              args_list, err);
+  }
+
+  // All other function types take a pre-executed set of args.
+  Value args = args_list->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+
+  if (found_function->second.generic_block_runner) {
+    if (!block) {
+      FillNeedsBlockError(function, err);
+      return Value();
+    }
+    return found_function->second.generic_block_runner(
+        scope, function, args.list_value(), block, err);
+  }
+
+  if (found_function->second.executed_block_runner) {
+    if (!block) {
+      FillNeedsBlockError(function, err);
+      return Value();
+    }
+
+    Scope block_scope(scope);
+    block->Execute(&block_scope, err);
+    if (err->has_error())
+      return Value();
+
+    Value result = found_function->second.executed_block_runner(
+        function, args.list_value(), &block_scope, err);
+    if (err->has_error())
+      return Value();
+
+    if (!block_scope.CheckForUnusedVars(err))
+      return Value();
+    return result;
+  }
+
+  // Otherwise it's a no-block function.
+  if (!VerifyNoBlockForFunctionCall(function, block, err))
+    return Value();
+  return found_function->second.no_block_runner(scope, function,
+                                                args.list_value(), err);
+}
+
+}  // namespace functions
diff --git a/src/gn/functions.h b/src/gn/functions.h
new file mode 100644 (file)
index 0000000..890dcc7
--- /dev/null
@@ -0,0 +1,554 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FUNCTIONS_H_
+#define TOOLS_GN_FUNCTIONS_H_
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+class Err;
+class BlockNode;
+class FunctionCallNode;
+class Label;
+class ListNode;
+class ParseNode;
+class Scope;
+class Value;
+
+// -----------------------------------------------------------------------------
+
+namespace functions {
+
+// This type of function invocation has no block and evaluates its arguments
+// itself rather than taking a pre-executed list. This allows us to implement
+// certain built-in functions.
+using SelfEvaluatingArgsFunction = Value (*)(Scope* scope,
+                                             const FunctionCallNode* function,
+                                             const ListNode* args_list,
+                                             Err* err);
+
+// This type of function invocation takes a block node that it will execute.
+using GenericBlockFunction = Value (*)(Scope* scope,
+                                       const FunctionCallNode* function,
+                                       const std::vector<Value>& args,
+                                       BlockNode* block,
+                                       Err* err);
+
+// This type of function takes a block, but does not need to control execution
+// of it. The dispatch function will pre-execute the block and pass the
+// resulting block_scope to the function.
+using ExecutedBlockFunction = Value (*)(const FunctionCallNode* function,
+                                        const std::vector<Value>& args,
+                                        Scope* block_scope,
+                                        Err* err);
+
+// This type of function does not take a block. It just has arguments.
+using NoBlockFunction = Value (*)(Scope* scope,
+                                  const FunctionCallNode* function,
+                                  const std::vector<Value>& args,
+                                  Err* err);
+
+extern const char kAction[];
+extern const char kAction_HelpShort[];
+extern const char kAction_Help[];
+Value RunAction(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                BlockNode* block,
+                Err* err);
+
+extern const char kActionForEach[];
+extern const char kActionForEach_HelpShort[];
+extern const char kActionForEach_Help[];
+Value RunActionForEach(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err);
+
+extern const char kAssert[];
+extern const char kAssert_HelpShort[];
+extern const char kAssert_Help[];
+Value RunAssert(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err);
+
+extern const char kBundleData[];
+extern const char kBundleData_HelpShort[];
+extern const char kBundleData_Help[];
+Value RunBundleData(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err);
+
+extern const char kCreateBundle[];
+extern const char kCreateBundle_HelpShort[];
+extern const char kCreateBundle_Help[];
+Value RunCreateBundle(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      BlockNode* block,
+                      Err* err);
+
+extern const char kConfig[];
+extern const char kConfig_HelpShort[];
+extern const char kConfig_Help[];
+Value RunConfig(const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Scope* block_scope,
+                Err* err);
+
+extern const char kCopy[];
+extern const char kCopy_HelpShort[];
+extern const char kCopy_Help[];
+Value RunCopy(const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              Scope* block_scope,
+              Err* err);
+
+extern const char kDeclareArgs[];
+extern const char kDeclareArgs_HelpShort[];
+extern const char kDeclareArgs_Help[];
+Value RunDeclareArgs(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err);
+
+extern const char kDefined[];
+extern const char kDefined_HelpShort[];
+extern const char kDefined_Help[];
+Value RunDefined(Scope* scope,
+                 const FunctionCallNode* function,
+                 const ListNode* args_list,
+                 Err* err);
+
+extern const char kExecScript[];
+extern const char kExecScript_HelpShort[];
+extern const char kExecScript_Help[];
+Value RunExecScript(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    Err* err);
+
+extern const char kExecutable[];
+extern const char kExecutable_HelpShort[];
+extern const char kExecutable_Help[];
+Value RunExecutable(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err);
+
+extern const char kFilterExclude[];
+extern const char kFilterExclude_HelpShort[];
+extern const char kFilterExclude_Help[];
+Value RunFilterExclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err);
+
+extern const char kFilterInclude[];
+extern const char kFilterInclude_HelpShort[];
+extern const char kFilterInclude_Help[];
+Value RunFilterInclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err);
+
+extern const char kForEach[];
+extern const char kForEach_HelpShort[];
+extern const char kForEach_Help[];
+Value RunForEach(Scope* scope,
+                 const FunctionCallNode* function,
+                 const ListNode* args_list,
+                 Err* err);
+
+extern const char kForwardVariablesFrom[];
+extern const char kForwardVariablesFrom_HelpShort[];
+extern const char kForwardVariablesFrom_Help[];
+Value RunForwardVariablesFrom(Scope* scope,
+                              const FunctionCallNode* function,
+                              const ListNode* args_list,
+                              Err* err);
+
+extern const char kGetEnv[];
+extern const char kGetEnv_HelpShort[];
+extern const char kGetEnv_Help[];
+Value RunGetEnv(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err);
+
+extern const char kGetLabelInfo[];
+extern const char kGetLabelInfo_HelpShort[];
+extern const char kGetLabelInfo_Help[];
+Value RunGetLabelInfo(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err);
+
+extern const char kGetPathInfo[];
+extern const char kGetPathInfo_HelpShort[];
+extern const char kGetPathInfo_Help[];
+Value RunGetPathInfo(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     Err* err);
+
+extern const char kGetTargetOutputs[];
+extern const char kGetTargetOutputs_HelpShort[];
+extern const char kGetTargetOutputs_Help[];
+Value RunGetTargetOutputs(Scope* scope,
+                          const FunctionCallNode* function,
+                          const std::vector<Value>& args,
+                          Err* err);
+
+extern const char kGeneratedFile[];
+extern const char kGeneratedFile_HelpShort[];
+extern const char kGeneratedFile_Help[];
+Value RunGeneratedFile(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err);
+
+extern const char kGroup[];
+extern const char kGroup_HelpShort[];
+extern const char kGroup_Help[];
+Value RunGroup(Scope* scope,
+               const FunctionCallNode* function,
+               const std::vector<Value>& args,
+               BlockNode* block,
+               Err* err);
+
+extern const char kImport[];
+extern const char kImport_HelpShort[];
+extern const char kImport_Help[];
+Value RunImport(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                Err* err);
+
+extern const char kLoadableModule[];
+extern const char kLoadableModule_HelpShort[];
+extern const char kLoadableModule_Help[];
+Value RunLoadableModule(Scope* scope,
+                        const FunctionCallNode* function,
+                        const std::vector<Value>& args,
+                        BlockNode* block,
+                        Err* err);
+
+extern const char kNotNeeded[];
+extern const char kNotNeeded_HelpShort[];
+extern const char kNotNeeded_Help[];
+Value RunNotNeeded(Scope* scope,
+                   const FunctionCallNode* function,
+                   const ListNode* args_list,
+                   Err* err);
+
+extern const char kPool[];
+extern const char kPool_HelpShort[];
+extern const char kPool_Help[];
+Value RunPool(const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              Scope* block_scope,
+              Err* err);
+
+extern const char kPrint[];
+extern const char kPrint_HelpShort[];
+extern const char kPrint_Help[];
+Value RunPrint(Scope* scope,
+               const FunctionCallNode* function,
+               const std::vector<Value>& args,
+               Err* err);
+
+extern const char kProcessFileTemplate[];
+extern const char kProcessFileTemplate_HelpShort[];
+extern const char kProcessFileTemplate_Help[];
+Value RunProcessFileTemplate(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err);
+
+extern const char kReadFile[];
+extern const char kReadFile_HelpShort[];
+extern const char kReadFile_Help[];
+Value RunReadFile(Scope* scope,
+                  const FunctionCallNode* function,
+                  const std::vector<Value>& args,
+                  Err* err);
+
+extern const char kRebasePath[];
+extern const char kRebasePath_HelpShort[];
+extern const char kRebasePath_Help[];
+Value RunRebasePath(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    Err* err);
+
+extern const char kRustLibrary[];
+extern const char kRustLibrary_HelpShort[];
+extern const char kRustLibrary_Help[];
+Value RunRustLibrary(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err);
+
+extern const char kRustProcMacro[];
+extern const char kRustProcMacro_HelpShort[];
+extern const char kRustProcMacro_Help[];
+Value RunRustProcMacro(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err);
+
+extern const char kSetDefaults[];
+extern const char kSetDefaults_HelpShort[];
+extern const char kSetDefaults_Help[];
+Value RunSetDefaults(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err);
+
+extern const char kSetDefaultToolchain[];
+extern const char kSetDefaultToolchain_HelpShort[];
+extern const char kSetDefaultToolchain_Help[];
+Value RunSetDefaultToolchain(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err);
+
+extern const char kSharedLibrary[];
+extern const char kSharedLibrary_HelpShort[];
+extern const char kSharedLibrary_Help[];
+Value RunSharedLibrary(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err);
+
+extern const char kSourceSet[];
+extern const char kSourceSet_HelpShort[];
+extern const char kSourceSet_Help[];
+Value RunSourceSet(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err);
+
+extern const char kSplitList[];
+extern const char kSplitList_HelpShort[];
+extern const char kSplitList_Help[];
+Value RunSplitList(Scope* scope,
+                   const FunctionCallNode* function,
+                   const ListNode* args_list,
+                   Err* err);
+
+extern const char kStaticLibrary[];
+extern const char kStaticLibrary_HelpShort[];
+extern const char kStaticLibrary_Help[];
+Value RunStaticLibrary(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err);
+
+extern const char kReplaceSubstr[];
+extern const char kReplaceSubstr_HelpShort[];
+extern const char kReplaceSubstr_Help[];
+Value RunReplaceSubstr(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args_list,
+                       Err* err);
+
+extern const char kTarget[];
+extern const char kTarget_HelpShort[];
+extern const char kTarget_Help[];
+Value RunTarget(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                BlockNode* block,
+                Err* err);
+
+extern const char kTemplate[];
+extern const char kTemplate_HelpShort[];
+extern const char kTemplate_Help[];
+Value RunTemplate(Scope* scope,
+                  const FunctionCallNode* function,
+                  const std::vector<Value>& args,
+                  BlockNode* block,
+                  Err* err);
+
+extern const char kTool[];
+extern const char kTool_HelpShort[];
+extern const char kTool_Help[];
+Value RunTool(Scope* scope,
+              const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              BlockNode* block,
+              Err* err);
+
+extern const char kToolchain[];
+extern const char kToolchain_HelpShort[];
+extern const char kToolchain_Help[];
+Value RunToolchain(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err);
+
+extern const char kWriteFile[];
+extern const char kWriteFile_HelpShort[];
+extern const char kWriteFile_Help[];
+Value RunWriteFile(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   Err* err);
+
+// -----------------------------------------------------------------------------
+
+// One function record. Only one of the given runner types will be non-null
+// which indicates the type of function it is.
+struct FunctionInfo {
+  FunctionInfo();
+  FunctionInfo(SelfEvaluatingArgsFunction seaf,
+               const char* in_help_short,
+               const char* in_help,
+               bool in_is_target);
+  FunctionInfo(GenericBlockFunction gbf,
+               const char* in_help_short,
+               const char* in_help,
+               bool in_is_target);
+  FunctionInfo(ExecutedBlockFunction ebf,
+               const char* in_help_short,
+               const char* in_help,
+               bool in_is_target);
+  FunctionInfo(NoBlockFunction nbf,
+               const char* in_help_short,
+               const char* in_help,
+               bool in_is_target);
+
+  SelfEvaluatingArgsFunction self_evaluating_args_runner;
+  GenericBlockFunction generic_block_runner;
+  ExecutedBlockFunction executed_block_runner;
+  NoBlockFunction no_block_runner;
+
+  const char* help_short;
+  const char* help;
+
+  bool is_target;
+};
+
+using FunctionInfoMap = std::map<std::string_view, FunctionInfo>;
+
+// Returns the mapping of all built-in functions.
+const FunctionInfoMap& GetFunctions();
+
+// Runs the given function.
+Value RunFunction(Scope* scope,
+                  const FunctionCallNode* function,
+                  const ListNode* args_list,
+                  BlockNode* block,  // Optional.
+                  Err* err);
+
+}  // namespace functions
+
+// Helper functions -----------------------------------------------------------
+
+// Validates that the scope that a value is defined in is not the scope
+// of the current declare_args() call, if that's what we're in. It is
+// illegal to read a value from inside the same declare_args() call, since
+// the overrides will not have been applied yet (see `gn help declare_args`
+// for more).
+bool EnsureNotReadingFromSameDeclareArgs(const ParseNode* node,
+                                         const Scope* cur_scope,
+                                         const Scope* val_scope,
+                                         Err* err);
+
+// Verifies that the current scope is not processing an import. If it is, it
+// will set the error, blame the given parse node for it, and return false.
+bool EnsureNotProcessingImport(const ParseNode* node,
+                               const Scope* scope,
+                               Err* err);
+
+// Like EnsureNotProcessingImport but checks for running the build config.
+bool EnsureNotProcessingBuildConfig(const ParseNode* node,
+                                    const Scope* scope,
+                                    Err* err);
+
+// Sets up the |block_scope| for executing a target (or something like it).
+// The |scope| is the containing scope. It should have been already set as the
+// parent for the |block_scope| when the |block_scope| was created.
+//
+// This will set up the target defaults and set the |target_name| variable in
+// the block scope to the current target name, which is assumed to be the first
+// argument to the function.
+//
+// On success, returns true. On failure, sets the error and returns false.
+bool FillTargetBlockScope(const Scope* scope,
+                          const FunctionCallNode* function,
+                          const std::string& target_type,
+                          const BlockNode* block,
+                          const std::vector<Value>& args,
+                          Scope* block_scope,
+                          Err* err);
+
+// Sets the given error to a message explaining that the function call requires
+// a block.
+void FillNeedsBlockError(const FunctionCallNode* function, Err* err);
+
+// Validates that the given function call has one string argument. This is
+// the most common function signature, so it saves space to have this helper.
+// Returns false and sets the error on failure.
+bool EnsureSingleStringArg(const FunctionCallNode* function,
+                           const std::vector<Value>& args,
+                           Err* err);
+
+// Returns the name of the toolchain for the given scope.
+const Label& ToolchainLabelForScope(const Scope* scope);
+
+// Generates a label for the given scope, using the current directory and
+// toolchain, and the given name.
+Label MakeLabelForScope(const Scope* scope,
+                        const FunctionCallNode* function,
+                        const std::string& name);
+
+// Some types of blocks can't be nested inside other ones. For such cases,
+// instantiate this object upon entering the block and Enter() will fail if
+// there is already another non-nestable block on the stack.
+class NonNestableBlock {
+ public:
+  // type_description is a string that will be used in error messages
+  // describing the type of the block, for example, "template" or "config".
+  NonNestableBlock(Scope* scope,
+                   const FunctionCallNode* function,
+                   const char* type_description);
+  ~NonNestableBlock();
+
+  bool Enter(Err* err);
+
+ private:
+  // Used as a void* key for the Scope to track our property. The actual value
+  // is never used.
+  static const int kKey;
+
+  Scope* scope_;
+  const FunctionCallNode* function_;
+  const char* type_description_;
+
+  // Set to true when the key is added to the scope so we don't try to
+  // delete nonexistent keys which will cause assertions.
+  bool key_added_;
+};
+
+#endif  // TOOLS_GN_FUNCTIONS_H_
diff --git a/src/gn/functions_target.cc b/src/gn/functions_target.cc
new file mode 100644 (file)
index 0000000..d781fd5
--- /dev/null
@@ -0,0 +1,1015 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+
+#include "gn/config_values_generator.h"
+#include "gn/err.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/target_generator.h"
+#include "gn/template.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+
+#define DEPENDENT_CONFIG_VARS \
+  "  Dependent configs: all_dependent_configs, public_configs\n"
+#define DEPS_VARS "  Deps: data_deps, deps, public_deps\n"
+#define GENERAL_TARGET_VARS                                                \
+  "  General: check_includes, configs, data, friend, inputs, metadata,\n"  \
+  "           output_name, output_extension, public, sources, testonly,\n" \
+  "           visibility\n"
+#define RUST_VARS \
+  "  Rust variables: aliased_deps, crate_root, crate_name\n"
+#define RUST_SHARED_VARS                                                 \
+  "  Rust variables: aliased_deps, crate_root, crate_name, crate_type\n"
+
+namespace functions {
+
+namespace {
+
+Value ExecuteGenericTarget(const char* target_type,
+                           Scope* scope,
+                           const FunctionCallNode* function,
+                           const std::vector<Value>& args,
+                           BlockNode* block,
+                           Err* err) {
+  NonNestableBlock non_nestable(scope, function, "target");
+  if (!non_nestable.Enter(err))
+    return Value();
+
+  if (!EnsureNotProcessingImport(function, scope, err) ||
+      !EnsureNotProcessingBuildConfig(function, scope, err))
+    return Value();
+  Scope block_scope(scope);
+  if (!FillTargetBlockScope(scope, function, target_type, block, args,
+                            &block_scope, err))
+    return Value();
+
+  block->Execute(&block_scope, err);
+  if (err->has_error())
+    return Value();
+
+  TargetGenerator::GenerateTarget(&block_scope, function, args, target_type,
+                                  err);
+  if (err->has_error())
+    return Value();
+
+  block_scope.CheckForUnusedVars(err);
+  return Value();
+}
+
+}  // namespace
+
+// action ----------------------------------------------------------------------
+
+// Common help paragraph on script runtime execution directories.
+#define SCRIPT_EXECUTION_CONTEXT                                              \
+  "\n"                                                                        \
+  "  The script will be executed with the given arguments with the current\n" \
+  "  directory being that of the root build directory. If you pass files\n"   \
+  "  to your script, see \"gn help rebase_path\" for how to convert\n"        \
+  "  file names to be relative to the build directory (file names in the\n"   \
+  "  sources, outputs, and inputs will be all treated as relative to the\n"   \
+  "  current build file and converted as needed automatically).\n"            \
+  "\n"                                                                        \
+  "  GN sets Ninja's flag 'restat = 1` for all action commands. This means\n" \
+  "  that Ninja will check the timestamp of the output after the action\n"    \
+  "  completes. If output timestamp is unchanged, the step will be treated\n" \
+  "  as if it never needed to be rebuilt, potentially eliminating some\n"     \
+  "  downstream steps for incremental builds. Scripts can improve build\n"    \
+  "  performance by taking care not to change the timstamp of the output\n"   \
+  "  file(s) if the contents have not changed.\n"
+
+// Common help paragraph on script output directories.
+#define SCRIPT_EXECUTION_OUTPUTS                                           \
+  "\n"                                                                     \
+  "  All output files must be inside the output directory of the build.\n" \
+  "  You would generally use |$target_out_dir| or |$target_gen_dir| to\n"  \
+  "  reference the output or generated intermediate file directories,\n"   \
+  "  respectively.\n"
+
+#define ACTION_DEPS                                                           \
+  "\n"                                                                        \
+  "  The \"deps\" and \"public_deps\" for an action will always be\n"         \
+  "  completed before any part of the action is run so it can depend on\n"    \
+  "  the output of previous steps. The \"data_deps\" will be built if the\n"  \
+  "  action is built, but may not have completed before all steps of the\n"   \
+  "  action are started. This can give additional parallelism in the build\n" \
+  "  for runtime-only dependencies.\n"
+
+// Common help paragraph on targets that can use different languages.
+#define LANGUAGE_HELP                                                     \
+  "\n"                                                                    \
+  "  The tools and commands used to create this target type will be\n"    \
+  "  determined by the source files in its sources. Targets containing\n" \
+  "  multiple compiler-incompatible languages are not allowed (e.g. a\n"  \
+  "  target containing both C and C++ sources is acceptable, but a\n"     \
+  "  target containing C and Rust sources is not).\n"
+
+const char kAction[] = "action";
+const char kAction_HelpShort[] =
+    "action: Declare a target that runs a script a single time.";
+const char kAction_Help[] =
+    R"(action: Declare a target that runs a script a single time.
+
+  This target type allows you to run a script a single time to produce one or
+  more output files. If you want to run a script once for each of a set of
+  input files, see "gn help action_foreach".
+
+Inputs
+
+  In an action the "sources" and "inputs" are treated the same: they're both
+  input dependencies on script execution with no special handling. If you want
+  to pass the sources to your script, you must do so explicitly by including
+  them in the "args". Note also that this means there is no special handling of
+  paths since GN doesn't know which of the args are paths and not. You will
+  want to use rebase_path() to convert paths to be relative to the
+  root_build_dir.
+
+  You can dynamically write input dependencies (for incremental rebuilds if an
+  input file changes) by writing a depfile when the script is run (see "gn help
+  depfile"). This is more flexible than "inputs".
+
+  If the command line length is very long, you can use response files to pass
+  args to your script. See "gn help response_file_contents".
+
+  It is recommended you put inputs to your script in the "sources" variable,
+  and stuff like other Python files required to run your script in the "inputs"
+  variable.
+)"
+
+    ACTION_DEPS
+
+    R"(
+Outputs
+
+  You should specify files created by your script by specifying them in the
+  "outputs".
+)"
+
+    SCRIPT_EXECUTION_CONTEXT
+
+    R"(
+File name handling
+)"
+
+    SCRIPT_EXECUTION_OUTPUTS
+
+    R"(
+Variables
+
+  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
+  response_file_contents, script*, sources
+  * = required
+
+Example
+
+  action("run_this_guy_once") {
+    script = "doprocessing.py"
+    sources = [ "my_configuration.txt" ]
+    outputs = [ "$target_gen_dir/insightful_output.txt" ]
+
+    # Our script imports this Python file so we want to rebuild if it changes.
+    inputs = [ "helper_library.py" ]
+
+    # Note that we have to manually pass the sources to our script if the
+    # script needs them as inputs.
+    args = [ "--out", rebase_path(target_gen_dir, root_build_dir) ] +
+           rebase_path(sources, root_build_dir)
+  }
+)";
+
+Value RunAction(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                BlockNode* block,
+                Err* err) {
+  return ExecuteGenericTarget(functions::kAction, scope, function, args, block,
+                              err);
+}
+
+// action_foreach --------------------------------------------------------------
+
+const char kActionForEach[] = "action_foreach";
+const char kActionForEach_HelpShort[] =
+    "action_foreach: Declare a target that runs a script over a set of files.";
+const char kActionForEach_Help[] =
+    R"(action_foreach: Declare a target that runs a script over a set of files.
+
+  This target type allows you to run a script once-per-file over a set of
+  sources. If you want to run a script once that takes many files as input, see
+  "gn help action".
+
+Inputs
+
+  The script will be run once per file in the "sources" variable. The "outputs"
+  variable should specify one or more files with a source expansion pattern in
+  it (see "gn help source_expansion"). The output file(s) for each script
+  invocation should be unique. Normally you use "{{source_name_part}}" in each
+  output file.
+
+  If your script takes additional data as input, such as a shared configuration
+  file or a Python module it uses, those files should be listed in the "inputs"
+  variable. These files are treated as dependencies of each script invocation.
+
+  If the command line length is very long, you can use response files to pass
+  args to your script. See "gn help response_file_contents".
+
+  You can dynamically write input dependencies (for incremental rebuilds if an
+  input file changes) by writing a depfile when the script is run (see "gn help
+  depfile"). This is more flexible than "inputs".
+)" ACTION_DEPS
+    R"(
+Outputs
+)" SCRIPT_EXECUTION_CONTEXT
+    R"(
+File name handling
+)" SCRIPT_EXECUTION_OUTPUTS
+    R"(
+Variables
+
+  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
+  response_file_contents, script*, sources*
+  * = required
+
+Example
+
+  # Runs the script over each IDL file. The IDL script will generate both a .cc
+  # and a .h file for each input.
+  action_foreach("my_idl") {
+    script = "idl_processor.py"
+    sources = [ "foo.idl", "bar.idl" ]
+
+    # Our script reads this file each time, so we need to list it as a
+    # dependency so we can rebuild if it changes.
+    inputs = [ "my_configuration.txt" ]
+
+    # Transformation from source file name to output file names.
+    outputs = [ "$target_gen_dir/{{source_name_part}}.h",
+                "$target_gen_dir/{{source_name_part}}.cc" ]
+
+    # Note that since "args" is opaque to GN, if you specify paths here, you
+    # will need to convert it to be relative to the build directory using
+    # rebase_path().
+    args = [
+      "{{source}}",
+      "-o",
+      rebase_path(target_gen_dir, root_build_dir) +
+        "/{{source_name_part}}.h" ]
+  }
+)";
+
+Value RunActionForEach(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) {
+  return ExecuteGenericTarget(functions::kActionForEach, scope, function, args,
+                              block, err);
+}
+
+// bundle_data -----------------------------------------------------------------
+
+const char kBundleData[] = "bundle_data";
+const char kBundleData_HelpShort[] =
+    "bundle_data: [iOS/macOS] Declare a target without output.";
+const char kBundleData_Help[] =
+    R"(bundle_data: [iOS/macOS] Declare a target without output.
+
+  This target type allows one to declare data that is required at runtime. It is
+  used to inform "create_bundle" targets of the files to copy into generated
+  bundle, see "gn help create_bundle" for help.
+
+  The target must define a list of files as "sources" and a single "outputs".
+  If there are multiple files, source expansions must be used to express the
+  output. The output must reference a file inside of {{bundle_root_dir}}.
+
+  This target can be used on all platforms though it is designed only to
+  generate iOS/macOS bundle. In cross-platform projects, it is advised to put it
+  behind iOS/macOS conditionals.
+
+  See "gn help create_bundle" for more information.
+
+Variables
+
+  sources*, outputs*, deps, data_deps, metadata, public_deps, visibility
+  * = required
+
+Examples
+
+  bundle_data("icudata") {
+    sources = [ "sources/data/in/icudtl.dat" ]
+    outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
+  }
+
+  bundle_data("base_unittests_bundle_data]") {
+    sources = [ "test/data" ]
+    outputs = [
+      "{{bundle_resources_dir}}/{{source_root_relative_dir}}/" +
+          "{{source_file_part}}"
+    ]
+  }
+
+  bundle_data("material_typography_bundle_data") {
+    sources = [
+      "src/MaterialTypography.bundle/Roboto-Bold.ttf",
+      "src/MaterialTypography.bundle/Roboto-Italic.ttf",
+      "src/MaterialTypography.bundle/Roboto-Regular.ttf",
+      "src/MaterialTypography.bundle/Roboto-Thin.ttf",
+    ]
+    outputs = [
+      "{{bundle_resources_dir}}/MaterialTypography.bundle/"
+          "{{source_file_part}}"
+    ]
+  }
+)";
+
+Value RunBundleData(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err) {
+  return ExecuteGenericTarget(functions::kBundleData, scope, function, args,
+                              block, err);
+}
+
+// create_bundle ---------------------------------------------------------------
+
+const char kCreateBundle[] = "create_bundle";
+const char kCreateBundle_HelpShort[] =
+    "create_bundle: [iOS/macOS] Build an iOS or macOS bundle.";
+const char kCreateBundle_Help[] =
+    R"(create_bundle: [ios/macOS] Build an iOS or macOS bundle.
+
+  This target generates an iOS or macOS bundle (which is a directory with a
+  well-know structure). This target does not define any sources, instead they
+  are computed from all "bundle_data" target this one depends on transitively
+  (the recursion stops at "create_bundle" targets).
+
+  The "bundle_*_dir" are be used for the expansion of {{bundle_*_dir}} rules in
+  "bundle_data" outputs. The properties are optional but must be defined if any
+  of the "bundle_data" target use them.
+
+  This target can be used on all platforms though it is designed only to
+  generate iOS or macOS bundle. In cross-platform projects, it is advised to put
+  it behind iOS/macOS conditionals.
+
+  If a create_bundle is specified as a data_deps for another target, the bundle
+  is considered a leaf, and its public and private dependencies will not
+  contribute to any data or data_deps. Required runtime dependencies should be
+  placed in the bundle. A create_bundle can declare its own explicit data and
+  data_deps, however.
+
+Code signing
+
+  Some bundle needs to be code signed as part of the build (on iOS all
+  application needs to be code signed to run on a device). The code signature
+  can be configured via the code_signing_script variable.
+
+  If set, code_signing_script is the path of a script that invoked after all
+  files have been moved into the bundle. The script must not change any file in
+  the bundle, but may add new files.
+
+  If code_signing_script is defined, then code_signing_outputs must also be
+  defined and non-empty to inform when the script needs to be re-run. The
+  code_signing_args will be passed as is to the script (so path have to be
+  rebased) and additional inputs may be listed with the variable
+  code_signing_sources.
+
+Variables
+
+  bundle_root_dir, bundle_contents_dir, bundle_resources_dir,
+  bundle_executable_dir, bundle_deps_filter, deps, data_deps, public_deps,
+  visibility, product_type, code_signing_args, code_signing_script,
+  code_signing_sources, code_signing_outputs, xcode_extra_attributes,
+  xcode_test_application_name, partial_info_plist, metadata
+
+Example
+
+  # Defines a template to create an application. On most platform, this is just
+  # an alias for an "executable" target, but on iOS/macOS, it builds an
+  # application bundle.
+  template("app") {
+    if (!is_ios && !is_mac) {
+      executable(target_name) {
+        forward_variables_from(invoker, "*")
+      }
+    } else {
+      app_name = target_name
+      gen_path = target_gen_dir
+
+      action("${app_name}_generate_info_plist") {
+        script = [ "//build/ios/ios_gen_plist.py" ]
+        sources = [ "templates/Info.plist" ]
+        outputs = [ "$gen_path/Info.plist" ]
+        args = rebase_path(sources, root_build_dir) +
+               rebase_path(outputs, root_build_dir)
+      }
+
+      bundle_data("${app_name}_bundle_info_plist") {
+        public_deps = [ ":${app_name}_generate_info_plist" ]
+        sources = [ "$gen_path/Info.plist" ]
+        outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
+      }
+
+      executable("${app_name}_generate_executable") {
+        forward_variables_from(invoker, "*", [
+                                               "output_name",
+                                               "visibility",
+                                             ])
+        output_name =
+            rebase_path("$gen_path/$app_name", root_build_dir)
+      }
+
+      code_signing =
+          defined(invoker.code_signing) && invoker.code_signing
+
+      if (!is_ios || !code_signing) {
+        bundle_data("${app_name}_bundle_executable") {
+          public_deps = [ ":${app_name}_generate_executable" ]
+          sources = [ "$gen_path/$app_name" ]
+          outputs = [ "{{bundle_executable_dir}}/$app_name" ]
+        }
+      }
+
+      create_bundle("$app_name.app") {
+        product_type = "com.apple.product-type.application"
+
+        if (is_ios) {
+          bundle_root_dir = "$root_build_dir/$target_name"
+          bundle_contents_dir = bundle_root_dir
+          bundle_resources_dir = bundle_contents_dir
+          bundle_executable_dir = bundle_contents_dir
+
+          xcode_extra_attributes = {
+            ONLY_ACTIVE_ARCH = "YES"
+            DEBUG_INFORMATION_FORMAT = "dwarf"
+          }
+        } else {
+          bundle_root_dir = "$root_build_dir/$target_name"
+          bundle_contents_dir  = "$bundle_root_dir/Contents"
+          bundle_resources_dir = "$bundle_contents_dir/Resources"
+          bundle_executable_dir = "$bundle_contents_dir/MacOS"
+        }
+        deps = [ ":${app_name}_bundle_info_plist" ]
+        if (is_ios && code_signing) {
+          deps += [ ":${app_name}_generate_executable" ]
+          code_signing_script = "//build/config/ios/codesign.py"
+          code_signing_sources = [
+            invoker.entitlements_path,
+            "$target_gen_dir/$app_name",
+          ]
+          code_signing_outputs = [
+            "$bundle_root_dir/$app_name",
+            "$bundle_root_dir/_CodeSignature/CodeResources",
+            "$bundle_root_dir/embedded.mobileprovision",
+            "$target_gen_dir/$app_name.xcent",
+          ]
+          code_signing_args = [
+            "-i=" + ios_code_signing_identity,
+            "-b=" + rebase_path(
+                "$target_gen_dir/$app_name", root_build_dir),
+            "-e=" + rebase_path(
+                invoker.entitlements_path, root_build_dir),
+            "-e=" + rebase_path(
+                "$target_gen_dir/$app_name.xcent", root_build_dir),
+            rebase_path(bundle_root_dir, root_build_dir),
+          ]
+        } else {
+          deps += [ ":${app_name}_bundle_executable" ]
+        }
+      }
+    }
+  }
+)";
+
+Value RunCreateBundle(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      BlockNode* block,
+                      Err* err) {
+  return ExecuteGenericTarget(functions::kCreateBundle, scope, function, args,
+                              block, err);
+}
+
+// copy ------------------------------------------------------------------------
+
+const char kCopy[] = "copy";
+const char kCopy_HelpShort[] = "copy: Declare a target that copies files.";
+const char kCopy_Help[] =
+    R"(copy: Declare a target that copies files.
+
+File name handling
+
+  All output files must be inside the output directory of the build. You would
+  generally use |$target_out_dir| or |$target_gen_dir| to reference the output
+  or generated intermediate file directories, respectively.
+
+  Both "sources" and "outputs" must be specified. Sources can include as many
+  files as you want, but there can only be one item in the outputs list (plural
+  is used for the name for consistency with other target types).
+
+  If there is more than one source file, your output name should specify a
+  mapping from each source file to an output file name using source expansion
+  (see "gn help source_expansion"). The placeholders will look like
+  "{{source_name_part}}", for example.
+
+Examples
+
+  # Write a rule that copies a checked-in DLL to the output directory.
+  copy("mydll") {
+    sources = [ "mydll.dll" ]
+    outputs = [ "$target_out_dir/mydll.dll" ]
+  }
+
+  # Write a rule to copy several files to the target generated files directory.
+  copy("myfiles") {
+    sources = [ "data1.dat", "data2.dat", "data3.dat" ]
+
+    # Use source expansion to generate output files with the corresponding file
+    # names in the gen dir. This will just copy each file.
+    outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+  }
+)";
+
+Value RunCopy(const FunctionCallNode* function,
+              const std::vector<Value>& args,
+              Scope* scope,
+              Err* err) {
+  if (!EnsureNotProcessingImport(function, scope, err) ||
+      !EnsureNotProcessingBuildConfig(function, scope, err))
+    return Value();
+  TargetGenerator::GenerateTarget(scope, function, args, functions::kCopy, err);
+  return Value();
+}
+
+// executable ------------------------------------------------------------------
+
+const char kExecutable[] = "executable";
+const char kExecutable_HelpShort[] =
+    "executable: Declare an executable target.";
+const char kExecutable_Help[] =
+    R"(executable: Declare an executable target.
+
+Language and compilation
+)" LANGUAGE_HELP
+    R"(
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_VARS;
+
+Value RunExecutable(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err) {
+  return ExecuteGenericTarget(functions::kExecutable, scope, function, args,
+                              block, err);
+}
+
+// group -----------------------------------------------------------------------
+
+const char kGroup[] = "group";
+const char kGroup_HelpShort[] = "group: Declare a named group of targets.";
+const char kGroup_Help[] =
+    R"(group: Declare a named group of targets.
+
+  This target type allows you to create meta-targets that just collect a set of
+  dependencies into one named target. Groups can additionally specify configs
+  that apply to their dependents.
+
+Variables
+
+)" DEPS_VARS DEPENDENT_CONFIG_VARS
+
+    R"(
+Example
+
+  group("all") {
+    deps = [
+      "//project:runner",
+      "//project:unit_tests",
+    ]
+  }
+)";
+
+Value RunGroup(Scope* scope,
+               const FunctionCallNode* function,
+               const std::vector<Value>& args,
+               BlockNode* block,
+               Err* err) {
+  return ExecuteGenericTarget(functions::kGroup, scope, function, args, block,
+                              err);
+}
+
+// loadable_module -------------------------------------------------------------
+
+const char kLoadableModule[] = "loadable_module";
+const char kLoadableModule_HelpShort[] =
+    "loadable_module: Declare a loadable module target.";
+const char kLoadableModule_Help[] =
+    R"(loadable_module: Declare a loadable module target.
+
+  This target type allows you to create an object file that is (and can only
+  be) loaded and unloaded at runtime.
+
+  A loadable module will be specified on the linker line for targets listing
+  the loadable module in its "deps". If you don't want this (if you don't need
+  to dynamically load the library at runtime), then you should use a
+  "shared_library" target type instead.
+
+Language and compilation
+)" LANGUAGE_HELP
+    R"(
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_SHARED_VARS;
+
+Value RunLoadableModule(Scope* scope,
+                        const FunctionCallNode* function,
+                        const std::vector<Value>& args,
+                        BlockNode* block,
+                        Err* err) {
+  return ExecuteGenericTarget(functions::kLoadableModule, scope, function, args,
+                              block, err);
+}
+
+// rust_library ----------------------------------------------------------------
+
+const char kRustLibrary[] = "rust_library";
+const char kRustLibrary_HelpShort[] =
+    "rust_library: Declare a Rust library target.";
+const char kRustLibrary_Help[] =
+    R"(rust_library: Declare a Rust library target.
+
+  A Rust library is an archive containing additional rust-c provided metadata.
+  These are the files produced by the rustc compiler with the `.rlib`
+  extension, and are the intermediate step for most Rust-based binaries.
+
+Language and compilation
+)" LANGUAGE_HELP
+    R"(
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_VARS;
+Value RunRustLibrary(Scope* scope,
+                     const FunctionCallNode* function,
+                     const std::vector<Value>& args,
+                     BlockNode* block,
+                     Err* err) {
+  return ExecuteGenericTarget(functions::kRustLibrary, scope, function, args,
+                              block, err);
+}
+
+// rust_proc_macro ----------------------------------------------------------------
+
+const char kRustProcMacro[] = "rust_proc_macro";
+const char kRustProcMacro_HelpShort[] =
+    "rust_proc_macro: Declare a Rust procedural macro target.";
+const char kRustProcMacro_Help[] =
+    R"(rust_proc_macro: Declare a Rust procedural macro target.
+
+  A Rust procedural macro allows creating syntax extensions as execution of a
+  function. They are compiled as dynamic libraries and used by the compiler at
+  runtime.
+
+  Their use is the same as of other Rust libraries, but their build has some
+  additional restrictions in terms of supported flags.
+
+Language and compilation
+)" LANGUAGE_HELP
+    R"(
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_VARS;
+Value RunRustProcMacro(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err) {
+  return ExecuteGenericTarget(functions::kRustProcMacro, scope, function, args,
+                              block, err);
+}
+
+// shared_library --------------------------------------------------------------
+
+const char kSharedLibrary[] = "shared_library";
+const char kSharedLibrary_HelpShort[] =
+    "shared_library: Declare a shared library target.";
+const char kSharedLibrary_Help[] =
+    R"(shared_library: Declare a shared library target.
+
+  A shared library will be specified on the linker line for targets listing the
+  shared library in its "deps". If you don't want this (say you dynamically
+  load the library at runtime), then you should depend on the shared library
+  via "data_deps" or, on Darwin platforms, use a "loadable_module" target type
+  instead.
+
+Language and compilation
+)" LANGUAGE_HELP
+    R"(
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_SHARED_VARS;
+
+Value RunSharedLibrary(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) {
+  return ExecuteGenericTarget(functions::kSharedLibrary, scope, function, args,
+                              block, err);
+}
+
+// source_set ------------------------------------------------------------------
+
+const char kSourceSet[] = "source_set";
+const char kSourceSet_HelpShort[] = "source_set: Declare a source set target.";
+const char kSourceSet_Help[] =
+    R"(source_set: Declare a source set target.
+
+  Only C-language source sets are supported at the moment.
+
+C-language source_sets
+
+  A source set is a collection of sources that get compiled, but are not linked
+  to produce any kind of library. Instead, the resulting object files are
+  implicitly added to the linker line of all targets that depend on the source
+  set.
+
+  In most cases, a source set will behave like a static library, except no
+  actual library file will be produced. This will make the build go a little
+  faster by skipping creation of a large static library, while maintaining the
+  organizational benefits of focused build targets.
+
+  The main difference between a source set and a static library is around
+  handling of exported symbols. Most linkers assume declaring a function
+  exported means exported from the static library. The linker can then do dead
+  code elimination to delete code not reachable from exported functions.
+
+  A source set will not do this code elimination since there is no link step.
+  This allows you to link many source sets into a shared library and have the
+  "exported symbol" notation indicate "export from the final shared library and
+  not from the intermediate targets." There is no way to express this concept
+  when linking multiple static libraries into a shared library.
+
+Variables
+
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS;
+
+Value RunSourceSet(Scope* scope,
+                   const FunctionCallNode* function,
+                   const std::vector<Value>& args,
+                   BlockNode* block,
+                   Err* err) {
+  return ExecuteGenericTarget(functions::kSourceSet, scope, function, args,
+                              block, err);
+}
+
+// static_library --------------------------------------------------------------
+
+const char kStaticLibrary[] = "static_library";
+const char kStaticLibrary_HelpShort[] =
+    "static_library: Declare a static library target.";
+const char kStaticLibrary_Help[] =
+    R"(static_library: Declare a static library target.
+
+  Make a ".a" / ".lib" file.
+
+  If you only need the static library for intermediate results in the build,
+  you should consider a source_set instead since it will skip the (potentially
+  slow) step of creating the intermediate library file.
+
+Variables
+
+  complete_static_lib
+)" CONFIG_VALUES_VARS_HELP DEPS_VARS DEPENDENT_CONFIG_VARS GENERAL_TARGET_VARS
+        RUST_VARS LANGUAGE_HELP;
+
+Value RunStaticLibrary(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) {
+  return ExecuteGenericTarget(functions::kStaticLibrary, scope, function, args,
+                              block, err);
+}
+
+// target ---------------------------------------------------------------------
+
+const char kTarget[] = "target";
+const char kTarget_HelpShort[] =
+    "target: Declare an target with the given programmatic type.";
+const char kTarget_Help[] =
+    R"(target: Declare an target with the given programmatic type.
+
+  target(target_type_string, target_name_string) { ... }
+
+  The target() function is a way to invoke a built-in target or template with a
+  type determined at runtime. This is useful for cases where the type of a
+  target might not be known statically.
+
+  Only templates and built-in target functions are supported for the
+  target_type_string parameter. Arbitrary functions, configs, and toolchains
+  are not supported.
+
+  The call:
+    target("source_set", "doom_melon") {
+  Is equivalent to:
+    source_set("doom_melon") {
+
+Example
+
+  if (foo_build_as_shared) {
+    my_type = "shared_library"
+  } else {
+    my_type = "source_set"
+  }
+
+  target(my_type, "foo") {
+    ...
+  }
+)";
+Value RunTarget(Scope* scope,
+                const FunctionCallNode* function,
+                const std::vector<Value>& args,
+                BlockNode* block,
+                Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expected two arguments.", "Try \"gn help target\".");
+    return Value();
+  }
+
+  // The first argument must be a string (the target type). Don't type-check
+  // the second argument since the target-specific function will do that.
+  if (!args[0].VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const std::string& target_type = args[0].string_value();
+
+  // The rest of the args are passed to the function.
+  std::vector<Value> sub_args(args.begin() + 1, args.end());
+
+  // Run a template if it is one.
+  const Template* templ = scope->GetTemplate(target_type);
+  if (templ)
+    return templ->Invoke(scope, function, target_type, sub_args, block, err);
+
+  // Otherwise, assume the target is a built-in target type.
+  return ExecuteGenericTarget(target_type.c_str(), scope, function, sub_args,
+                              block, err);
+}
+
+const char kGeneratedFile[] = "generated_file";
+const char kGeneratedFile_HelpShort[] =
+    "generated_file: Declare a generated_file target.";
+const char kGeneratedFile_Help[] =
+    R"(generated_file: Declare a generated_file target.
+
+  Writes data value(s) to disk on resolution. This target type mirrors some
+  functionality of the write_file() function, but also provides the ability to
+  collect metadata from its dependencies on resolution rather than writing out
+  at parse time.
+
+  The `outputs` variable is required to be a list with a single element,
+  specifying the intended location of the output file.
+
+  The `output_conversion` variable specified the format to write the
+  value. See `gn help output_conversion`.
+
+  One of `contents` or `data_keys` must be specified; use of `data` will write
+  the contents of that value to file, while use of `data_keys` will trigger a
+  metadata collection walk based on the dependencies of the target and the
+  optional values of the `rebase` and `walk_keys` variables. See
+  `gn help metadata`.
+
+  Collected metadata, if specified, will be returned in postorder of
+  dependencies. See the example for details.
+
+Example (metadata collection)
+
+  Given the following targets defined in //base/BUILD.gn, where A depends on B
+  and B depends on C and D:
+
+    group("a") {
+      metadata = {
+        doom_melon = [ "enable" ]
+        my_files = [ "foo.cpp" ]
+
+        # Note: this is functionally equivalent to not defining `my_barrier`
+        # at all in this target's metadata.
+        my_barrier = [ "" ]
+      }
+
+      deps = [ ":b" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "bar.cpp" ]
+        my_barrier = [ ":c" ]
+      }
+
+      deps = [ ":c", ":d" ]
+    }
+
+    group("c") {
+      metadata = {
+        doom_melon = [ "disable" ]
+        my_files = [ "baz.cpp" ]
+      }
+    }
+
+    group("d") {
+      metadata = {
+        my_files = [ "missing.cpp" ]
+      }
+    }
+
+  If the following generated_file target is defined:
+
+    generated_file("my_files_metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files" ]
+
+      deps = [ "//base:a" ]
+    }
+
+  The following will be written to "$root_build_dir/my_files.json" (less the
+  comments):
+    [
+      "baz.cpp",  // from //base:c via //base:b
+      "missing.cpp"  // from //base:d via //base:b
+      "bar.cpp",  // from //base:b via //base:a
+      "foo.cpp",  // from //base:a
+    ]
+
+  Alternatively, as an example of using walk_keys, if the following
+  generated_file target is defined:
+
+  generated_file("my_files_metadata") {
+    outputs = [ "$root_build_dir/my_files.json" ]
+    data_keys = [ "my_files" ]
+    walk_keys = [ "my_barrier" ]
+
+    deps = [ "//base:a" ]
+  }
+
+  The following will be written to "$root_build_dir/my_files.json" (again less
+  the comments):
+    [
+      "baz.cpp",  // from //base:c via //base:b
+      "bar.cpp",  // from //base:b via //base:a
+      "foo.cpp",  // from //base:a
+    ]
+
+  If `rebase` is used in the following generated_file target:
+
+  generated_file("my_files_metadata") {
+    outputs = [ "$root_build_dir/my_files.json" ]
+    data_keys = [ "my_files" ]
+    walk_keys = [ "my_barrier" ]
+    rebase = root_build_dir
+
+    deps = [ "//base:a" ]
+  }
+
+  The following will be written to "$root_build_dir/my_files.json" (again less
+  the comments) (assuming root_build_dir = "//out"):
+    [
+      "../base/baz.cpp",  // from //base:c via //base:b
+      "../base/bar.cpp",  // from //base:b via //base:a
+      "../base/foo.cpp",  // from //base:a
+    ]
+
+
+Variables
+
+  contents
+  data_keys
+  rebase
+  walk_keys
+  output_conversion
+)" DEPS_VARS DEPENDENT_CONFIG_VARS;
+
+Value RunGeneratedFile(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) {
+  return ExecuteGenericTarget(functions::kGeneratedFile, scope, function, args,
+                              block, err);
+}
+
+}  // namespace functions
diff --git a/src/gn/functions_target_rust_unittest.cc b/src/gn/functions_target_rust_unittest.cc
new file mode 100644 (file)
index 0000000..a8239aa
--- /dev/null
@@ -0,0 +1,324 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/config.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using RustFunctionsTarget = TestWithScheduler;
+
+// Checks that the appropriate crate type is used.
+TEST_F(RustFunctionsTarget, CrateName) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput exe_input(
+      "executable(\"foo\") {\n"
+      "  crate_name = \"foo_crate\"\n"
+      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  Err err;
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_name(),
+            "foo_crate");
+
+  TestParseInput lib_input(
+      "executable(\"foo\") {\n"
+      "  sources = [ \"lib.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(lib_input.has_error());
+  err = Err();
+  lib_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_name(),
+            "foo")
+      << item_collector.back()->AsTarget()->rust_values().crate_name();
+}
+
+// Checks that the appropriate crate root is found.
+TEST_F(RustFunctionsTarget, CrateRootFind) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput normal_input(
+      "executable(\"foo\") {\n"
+      "  crate_root = \"foo.rs\""
+      "  sources = [ \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(normal_input.has_error());
+  Err err;
+  normal_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/foo.rs");
+
+  TestParseInput normal_shlib_input(
+      "shared_library(\"foo\") {\n"
+      "  crate_root = \"foo.rs\""
+      "  crate_type = \"dylib\"\n"
+      "  sources = [ \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(normal_shlib_input.has_error());
+  err = Err();
+  normal_shlib_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/foo.rs");
+
+  TestParseInput exe_input(
+      "executable(\"foo\") {\n"
+      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  err = Err();
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/main.rs");
+
+  TestParseInput lib_input(
+      "rust_library(\"libfoo\") {\n"
+      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(lib_input.has_error());
+  err = Err();
+  lib_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/lib.rs");
+
+  TestParseInput singlesource_input(
+      "executable(\"bar\") {\n"
+      "  sources = [ \"bar.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(singlesource_input.has_error());
+  err = Err();
+  singlesource_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/bar.rs");
+
+  TestParseInput error_input(
+      "rust_library(\"foo\") {\n"
+      "  sources = [ \"foo.rs\", \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(error_input.has_error());
+  err = Err();
+  error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Missing \"crate_root\" and missing \"lib.rs\" in sources.",
+            err.message());
+
+  TestParseInput nosources_input(
+      "executable(\"bar\") {\n"
+      "  crate_root = \"bar.rs\"\n"
+      "}\n");
+  ASSERT_FALSE(nosources_input.has_error());
+  err = Err();
+  nosources_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(
+      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
+      "/bar.rs");
+}
+
+// Checks that the appropriate crate type is used.
+TEST_F(RustFunctionsTarget, CrateTypeSelection) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput lib_input(
+      "shared_library(\"libfoo\") {\n"
+      "  crate_type = \"dylib\"\n"
+      "  sources = [ \"lib.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(lib_input.has_error());
+  Err err;
+  lib_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
+            RustValues::CRATE_DYLIB);
+
+  TestParseInput exe_non_default_input(
+      "executable(\"foo\") {\n"
+      "  crate_type = \"rlib\"\n"
+      "  sources = [ \"main.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(exe_non_default_input.has_error());
+  err = Err();
+  exe_non_default_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
+            RustValues::CRATE_RLIB);
+
+  TestParseInput lib_error_input(
+      "shared_library(\"foo\") {\n"
+      "  crate_type = \"bad\"\n"
+      "  sources = [ \"lib.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(lib_error_input.has_error());
+  err = Err();
+  lib_error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Inadmissible crate type \"bad\".", err.message()) << err.message();
+
+  TestParseInput lib_missing_error_input(
+      "shared_library(\"foo\") {\n"
+      "  sources = [ \"lib.rs\" ]\n"
+      "}\n");
+  ASSERT_FALSE(lib_missing_error_input.has_error());
+  err = Err();
+  lib_missing_error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Must set \"crate_type\" on a Rust \"shared_library\".",
+            err.message());
+}
+
+// Checks that the appropriate config values are propagated.
+TEST_F(RustFunctionsTarget, ConfigValues) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput exe_input(
+      "config(\"foo\") {\n"
+      "  rustflags = [ \"-Cdebuginfo=2\" ]\n"
+      "  rustenv = [ \"RUST_BACKTRACE=1\" ]"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  Err err;
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustflags().size(),
+            1U);
+  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustflags()[0],
+            "-Cdebuginfo=2");
+  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustenv().size(),
+            1U);
+  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustenv()[0],
+            "RUST_BACKTRACE=1");
+}
+
+// Checks that set_defaults works properly.
+TEST_F(RustFunctionsTarget, SetDefaults) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput exe_input(
+      "config(\"foo\") {\n"
+      "  rustflags = [ \"-Cdebuginfo=2\" ]\n"
+      "  rustenv = [ \"RUST_BACKTRACE=1\" ]"
+      "}\n"
+      "set_defaults(\"rust_library\") {\n"
+      "  configs = [ \":foo\" ]\n"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  Err err;
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message() << err.message();
+
+  EXPECT_EQ(setup.scope()
+                ->GetTargetDefaults("rust_library")
+                ->GetValue("configs")
+                ->type(),
+            Value::LIST);
+  EXPECT_EQ(setup.scope()
+                ->GetTargetDefaults("rust_library")
+                ->GetValue("configs")
+                ->list_value()
+                .size(),
+            1U);
+  EXPECT_EQ(setup.scope()
+                ->GetTargetDefaults("rust_library")
+                ->GetValue("configs")
+                ->list_value()[0]
+                .type(),
+            Value::STRING);
+  EXPECT_EQ(setup.scope()
+                ->GetTargetDefaults("rust_library")
+                ->GetValue("configs")
+                ->list_value()[0]
+                .string_value(),
+            ":foo");
+}
+
+// Checks aliased_deps parsing.
+TEST_F(RustFunctionsTarget, AliasedDeps) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput exe_input(
+      "executable(\"foo\") {\n"
+      "  sources = [ \"main.rs\" ]\n"
+      "  deps = [ \"//bar\", \"//baz\" ]\n"
+      "  aliased_deps = {\n"
+      "    bar_renamed = \"//bar\"\n"
+      "    baz_renamed = \"//baz:baz\"\n"
+      "  }\n"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  Err err;
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+      item_collector.back()->AsTarget()->rust_values().aliased_deps().size(),
+      2U);
+}
+
+TEST_F(RustFunctionsTarget, PublicConfigs) {
+  TestWithScope setup;
+
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+  setup.scope()->set_source_dir(SourceDir("/"));
+
+  TestParseInput exe_input(
+      "config(\"bar\") {\n"
+      "  defines = [ \"DOOM_MELON\" ]"
+      "}\n"
+      "executable(\"foo\") {\n"
+      "  crate_name = \"foo_crate\"\n"
+      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
+      "  public_configs = [ \":bar\" ]"
+      "}\n");
+  ASSERT_FALSE(exe_input.has_error());
+  Err err;
+  exe_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+}
diff --git a/src/gn/functions_target_unittest.cc b/src/gn/functions_target_unittest.cc
new file mode 100644 (file)
index 0000000..04b2a4b
--- /dev/null
@@ -0,0 +1,206 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using FunctionsTarget = TestWithScheduler;
+
+// Checks that we find unused identifiers in targets.
+TEST_F(FunctionsTarget, CheckUnused) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+
+  // Test a good one first.
+  TestParseInput good_input(
+      "source_set(\"foo\") {\n"
+      "}\n");
+  ASSERT_FALSE(good_input.has_error());
+  Err err;
+  good_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // Test a source set with an unused variable.
+  TestParseInput source_set_input(
+      "source_set(\"foo\") {\n"
+      "  unused = 5\n"
+      "}\n");
+  ASSERT_FALSE(source_set_input.has_error());
+  err = Err();
+  source_set_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+}
+
+// Checks that we find uses of identifiers marked as not needed.
+TEST_F(FunctionsTarget, CheckNotNeeded) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+
+  TestParseInput nonscoped_input(
+      "source_set(\"foo\") {\n"
+      "  a = 1\n"
+      "  not_needed([ \"a\" ])\n"
+      "}\n");
+  ASSERT_FALSE(nonscoped_input.has_error());
+  Err err;
+  nonscoped_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  TestParseInput scoped_input(
+      "source_set(\"foo\") {\n"
+      "  a = {x = 1 y = 2}\n"
+      "  not_needed(a, \"*\")\n"
+      "}\n");
+  ASSERT_FALSE(scoped_input.has_error());
+  err = Err();
+  scoped_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  TestParseInput nonexistent_arg_input(
+      "source_set(\"foo\") {\n"
+      "  a = {x = 1}\n"
+      "  not_needed(a, [ \"x\", \"y\" ])\n"
+      "}\n");
+  ASSERT_FALSE(nonexistent_arg_input.has_error());
+  err = Err();
+  nonexistent_arg_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  TestParseInput exclusion_input(
+      "source_set(\"foo\") {\n"
+      "  x = 1\n"
+      "  y = 2\n"
+      "  not_needed(\"*\", [ \"y\" ])\n"
+      "}\n");
+  ASSERT_FALSE(exclusion_input.has_error());
+  err = Err();
+  exclusion_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error()) << err.message();
+  EXPECT_EQ("Assignment had no effect.", err.message());
+
+  TestParseInput error_input(
+      "source_set(\"foo\") {\n"
+      "  a = {x = 1 y = 2}\n"
+      "  not_needed(a, [ \"x \"], [ \"y\" ])\n"
+      "}\n");
+  ASSERT_FALSE(error_input.has_error());
+  err = Err();
+  error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Not supported with a variable list.", err.message());
+
+  TestParseInput argcount_error_input(
+      "source_set(\"foo\") {\n"
+      "  not_needed()\n"
+      "}\n");
+  ASSERT_FALSE(argcount_error_input.has_error());
+  err = Err();
+  argcount_error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Wrong number of arguments.", err.message());
+
+  TestParseInput scope_error_input(
+      "source_set(\"foo\") {\n"
+      "  a = {x = 1 y = 2}\n"
+      "  not_needed(a)\n"
+      "}\n");
+  ASSERT_FALSE(scope_error_input.has_error());
+  err = Err();
+  scope_error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Wrong number of arguments.", err.message());
+
+  TestParseInput string_error_input(
+      "source_set(\"foo\") {\n"
+      "  not_needed(\"*\", {}, \"*\")\n"
+      "}\n");
+  ASSERT_FALSE(string_error_input.has_error());
+  err = Err();
+  string_error_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Wrong number of arguments.", err.message());
+
+  TestParseInput template_input(
+      R"(# Test that not_needed() propagates through templates correctly;
+      # no error should arise from not using "a".
+      template("inner_templ") {
+        source_set(target_name) {
+          not_needed(invoker, [ "a" ])
+        }
+      }
+      template("outer_templ") {
+        inner_templ(target_name) {
+          forward_variables_from(invoker, "*")
+        }
+      }
+      outer_templ("foo") {
+        a = 1
+      })");
+  ASSERT_FALSE(template_input.has_error());
+  err = Err();
+  template_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+}
+
+// Checks that the defaults applied to a template invoked by target() use
+// the name of the template, rather than the string "target" (which is the
+// name of the actual function being called).
+TEST_F(FunctionsTarget, TemplateDefaults) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+
+  // Test a good one first.
+  TestParseInput good_input(
+      R"(# Make a template with defaults set.
+      template("my_templ") {
+        source_set(target_name) {
+          forward_variables_from(invoker, "*")
+        }
+      }
+      set_defaults("my_templ") {
+        default_value = 1
+      }
+
+      # Invoke the template with target(). This will fail to execute if the
+      # defaults were not set properly, because "default_value" won't exist.
+      target("my_templ", "foo") {
+        print(default_value)
+      })");
+  ASSERT_FALSE(good_input.has_error());
+  Err err;
+  good_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+}
+
+// Checks that we find unused identifiers in targets.
+TEST_F(FunctionsTarget, MixedSourceError) {
+  TestWithScope setup;
+
+  // The target generator needs a place to put the targets or it will fail.
+  Scope::ItemVector item_collector;
+  setup.scope()->set_item_collector(&item_collector);
+
+  // Test a good one first.
+  TestParseInput good_input(
+      "source_set(\"foo\") {\n"
+      "  sources = [ \"cpp.cc\", \"rust.rs\" ]"
+      "}\n");
+  ASSERT_FALSE(good_input.has_error());
+  Err err;
+  good_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "More than one language used in target sources.");
+}
diff --git a/src/gn/functions_unittest.cc b/src/gn/functions_unittest.cc
new file mode 100644 (file)
index 0000000..40ac5d9
--- /dev/null
@@ -0,0 +1,459 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/functions.h"
+
+#include <memory>
+#include <utility>
+
+#include "gn/parse_tree.h"
+#include "gn/test_with_scope.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+TEST(Functions, Assert) {
+  TestWithScope setup;
+
+  // Verify cases where the assertion passes.
+  std::vector<std::string> assert_pass_examples = {
+    R"gn(assert(true))gn",
+    R"gn(assert(true, "This message is ignored for passed assertions."))gn",
+  };
+  for (const auto& assert_pass_example : assert_pass_examples) {
+    TestParseInput input(assert_pass_example);
+    ASSERT_FALSE(input.has_error());
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << assert_pass_example;
+  }
+
+  // Verify case where the assertion fails, with no message.
+  {
+    TestParseInput input("assert(false)");
+    ASSERT_FALSE(input.has_error());
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error());
+    ASSERT_EQ(err.message(), "Assertion failed.");
+  }
+
+  // Verify case where the assertion fails, with a message.
+  {
+    TestParseInput input("assert(false, \"What failed\")");
+    ASSERT_FALSE(input.has_error());
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error());
+    ASSERT_EQ(err.message(), "Assertion failed.");
+    ASSERT_EQ(err.help_text(), "What failed");
+  }
+
+  // Verify usage errors are detected.
+  std::vector<std::string> bad_usage_examples = {
+    // Number of arguments.
+    R"gn(assert())gn",
+    R"gn(assert(1, 2, 3))gn",
+
+    // Argument types.
+    R"gn(assert(1))gn",
+    R"gn(assert("oops"))gn",
+    R"gn(assert(true, 1))gn",
+    R"gn(assert(true, []))gn",
+  };
+  for (const auto& bad_usage_example : bad_usage_examples) {
+    TestParseInput input(bad_usage_example);
+    ASSERT_FALSE(input.has_error());
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << bad_usage_example;
+    // We are checking for usage errors, not assertion failures.
+    ASSERT_NE(err.message(), "Assertion failed.") << bad_usage_example;
+  }
+}
+
+TEST(Functions, Defined) {
+  TestWithScope setup;
+
+  FunctionCallNode function_call;
+  Err err;
+
+  // Test an undefined identifier.
+  Token undefined_token(Location(), Token::IDENTIFIER, "undef");
+  ListNode args_list_identifier_undefined;
+  args_list_identifier_undefined.append_item(
+      std::make_unique<IdentifierNode>(undefined_token));
+  Value result = functions::RunDefined(setup.scope(), &function_call,
+                                       &args_list_identifier_undefined, &err);
+  ASSERT_EQ(Value::BOOLEAN, result.type());
+  EXPECT_FALSE(result.boolean_value());
+
+  // Define a value that's itself a scope value.
+  const char kDef[] = "def";  // Defined variable name.
+  setup.scope()->SetValue(
+      kDef, Value(nullptr, std::make_unique<Scope>(setup.scope())), nullptr);
+
+  // Test the defined identifier.
+  Token defined_token(Location(), Token::IDENTIFIER, kDef);
+  ListNode args_list_identifier_defined;
+  args_list_identifier_defined.append_item(
+      std::make_unique<IdentifierNode>(defined_token));
+  result = functions::RunDefined(setup.scope(), &function_call,
+                                 &args_list_identifier_defined, &err);
+  ASSERT_EQ(Value::BOOLEAN, result.type());
+  EXPECT_TRUE(result.boolean_value());
+
+  // Should also work by passing an accessor node so you can do
+  // "defined(def.foo)" to see if foo is defined on the def scope.
+  std::unique_ptr<AccessorNode> undef_accessor =
+      std::make_unique<AccessorNode>();
+  undef_accessor->set_base(defined_token);
+  undef_accessor->set_member(std::make_unique<IdentifierNode>(undefined_token));
+  ListNode args_list_accessor_defined;
+  args_list_accessor_defined.append_item(std::move(undef_accessor));
+  result = functions::RunDefined(setup.scope(), &function_call,
+                                 &args_list_accessor_defined, &err);
+  ASSERT_EQ(Value::BOOLEAN, result.type());
+  EXPECT_FALSE(result.boolean_value());
+}
+
+// Tests that an error is thrown when a {} is supplied to a function that
+// doesn't take one.
+TEST(Functions, FunctionsWithBlock) {
+  TestWithScope setup;
+  Err err;
+
+  // No scope to print() is OK.
+  TestParseInput print_no_scope("print(6)");
+  EXPECT_FALSE(print_no_scope.has_error());
+  Value result = print_no_scope.parsed()->Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Passing a scope should pass parsing (it doesn't know about what kind of
+  // function it is) and then throw an error during execution.
+  TestParseInput print_with_scope("print(foo) {}");
+  EXPECT_FALSE(print_with_scope.has_error());
+  result = print_with_scope.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+
+  // defined() is a special function so test it separately.
+  TestParseInput defined_no_scope("defined(foo)");
+  EXPECT_FALSE(defined_no_scope.has_error());
+  result = defined_no_scope.parsed()->Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // A block to defined should fail.
+  TestParseInput defined_with_scope("defined(foo) {}");
+  EXPECT_FALSE(defined_with_scope.has_error());
+  result = defined_with_scope.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(Functions, SplitList) {
+  TestWithScope setup;
+
+  TestParseInput input(
+      // Empty input with varying result items.
+      "out1 = split_list([], 1)\n"
+      "out2 = split_list([], 3)\n"
+      "print(\"empty = $out1 $out2\")\n"
+
+      // One item input.
+      "out3 = split_list([1], 1)\n"
+      "out4 = split_list([1], 2)\n"
+      "print(\"one = $out3 $out4\")\n"
+
+      // Multiple items.
+      "out5 = split_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 2)\n"
+      "print(\"many = $out5\")\n"
+
+      // Rounding.
+      "out6 = split_list([1, 2, 3, 4, 5, 6], 4)\n"
+      "print(\"rounding = $out6\")\n");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+      "empty = [[]] [[], [], []]\n"
+      "one = [[1]] [[1], []]\n"
+      "many = [[1, 2, 3, 4, 5], [6, 7, 8, 9]]\n"
+      "rounding = [[1, 2], [3, 4], [5], [6]]\n",
+      setup.print_output());
+}
+
+TEST(Functions, StringJoin) {
+  TestWithScope setup;
+
+  // Verify outputs when string_join() is called correctly.
+  {
+    TestParseInput input(R"gn(
+        # No elements in the list and empty separator.
+        print("<" + string_join("", []) + ">")
+
+        # No elements in the list.
+        print("<" + string_join(" ", []) + ">")
+
+        # One element in the list.
+        print(string_join("|", ["a"]))
+
+        # Multiple elements in the list.
+        print(string_join(" ", ["a", "b", "c"]))
+
+        # Multi-character separator.
+        print(string_join("-.", ["a", "b", "c"]))
+
+        # Empty separator.
+        print(string_join("", ["x", "y", "z"]))
+
+        # Empty string list elements.
+        print(string_join("x", ["", "", ""]))
+
+        # Empty string list elements and separator
+        print(string_join("", ["", "", ""]))
+        )gn");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << err.message();
+
+    EXPECT_EQ(
+        "<>\n"
+        "<>\n"
+        "a\n"
+        "a b c\n"
+        "a-.b-.c\n"
+        "xyz\n"
+        "xx\n"
+        "\n",
+        setup.print_output()) << setup.print_output();
+  }
+
+  // Verify usage errors are detected.
+  std::vector<std::string> bad_usage_examples = {
+    // Number of arguments.
+    R"gn(string_join())gn",
+    R"gn(string_join(["oops"]))gn",
+    R"gn(string_join("kk", [], "oops"))gn",
+
+    // Argument types.
+    R"gn(string_join(1, []))gn",
+    R"gn(string_join("kk", "oops"))gn",
+    R"gn(string_join(["oops"], []))gn",
+
+    // Non-string elements in list of strings.
+    R"gn(string_join("kk", [1]))gn",
+    R"gn(string_join("kk", ["hello", 1]))gn",
+    R"gn(string_join("kk", ["hello", []]))gn",
+  };
+  for (const auto& bad_usage_example : bad_usage_examples) {
+    TestParseInput input(bad_usage_example);
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << bad_usage_example;
+  }
+}
+
+TEST(Functions, StringReplace) {
+  TestWithScope setup;
+
+  TestParseInput input(
+      // Replace all occurrences of string.
+      "out1 = string_replace(\"abbcc\", \"b\", \"d\")\n"
+      "print(out1)\n"
+
+      // Replace only the first occurrence.
+      "out2 = string_replace(\"abbcc\", \"b\", \"d\", 1)\n"
+      "print(out2)\n"
+
+      // Duplicate string to be replaced.
+      "out3 = string_replace(\"abbcc\", \"b\", \"bb\")\n"
+      "print(out3)\n"
+
+      // Handle overlapping occurrences.
+      "out4 = string_replace(\"aaa\", \"aa\", \"b\")\n"
+      "print(out4)\n");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+      "addcc\n"
+      "adbcc\n"
+      "abbbbcc\n"
+      "ba\n",
+      setup.print_output());
+}
+
+TEST(Functions, StringSplit) {
+  TestWithScope setup;
+
+  // Verify outputs when string_join() is called correctly.
+  {
+    TestParseInput input(R"gn(
+        # Split on all whitespace: empty string.
+        print(string_split(""))
+
+        # Split on all whitespace: string is only whitespace
+        print(string_split("      "))
+
+        # Split on all whitespace: leading, trailing, runs; one element.
+        print(string_split("hello"))
+        print(string_split("  hello"))
+        print(string_split("  hello   "))
+        print(string_split("hello   "))
+
+        # Split on all whitespace: leading, trailing, runs; multiple elements.
+        print(string_split("a b"))          # Pre-stripped
+        print(string_split("  a b"))        # Leading whitespace
+        print(string_split("  a b  "))      # Leading & trailing whitespace
+        print(string_split("a b  "))        # Trailing whitespace
+        print(string_split("a  b  "))       # Whitespace run between words
+        print(string_split(" a b cc ddd"))  # More & multi-character elements
+
+        # Split on string.
+        print(string_split("", "|"))           # Empty string
+        print(string_split("|", "|"))          # Only a separator
+        print(string_split("||", "|"))         # Only separators
+        print(string_split("ab", "|"))         # String is missing separator
+        print(string_split("a|b", "|"))        # Two elements
+        print(string_split("|a|b", "|"))       # Leading separator
+        print(string_split("a|b|", "|"))       # Trailing separator
+        print(string_split("||x", "|"))        # Leading consecutive separators
+        print(string_split("x||", "|"))        # Trailing consecutive separators
+        print(string_split("a|bb|ccc", "|"))   # Multiple elements
+        print(string_split(".x.x.x.", ".x."))  # Self-overlapping separators 1
+        print(string_split("x.x.x.", ".x."))   # Self-overlapping separators 2
+        )gn");
+    ASSERT_FALSE(input.has_error());
+
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_FALSE(err.has_error()) << err.message();
+
+    EXPECT_EQ(
+        // Split on all whitespace: empty string.
+        "[]\n"
+
+        // Split on all whitespace: string is only whitespace.
+        "[]\n"
+
+        // Split on all whitespace: leading, trailing, runs; one element.
+        "[\"hello\"]\n"
+        "[\"hello\"]\n"
+        "[\"hello\"]\n"
+        "[\"hello\"]\n"
+
+        // Split on all whitespace: leading, trailing, runs; multiple elements.
+        "[\"a\", \"b\"]\n"
+        "[\"a\", \"b\"]\n"
+        "[\"a\", \"b\"]\n"
+        "[\"a\", \"b\"]\n"
+        "[\"a\", \"b\"]\n"
+        "[\"a\", \"b\", \"cc\", \"ddd\"]\n"
+
+        // Split on string.
+        "[\"\"]\n"                   // Empty string (like Python)
+        "[\"\", \"\"]\n"             // Only a separator
+        "[\"\", \"\", \"\"]\n"       // Only separators
+        "[\"ab\"]\n"                 // String is missing separator
+        "[\"a\", \"b\"]\n"           // Two elements
+        "[\"\", \"a\", \"b\"]\n"     // Leading
+        "[\"a\", \"b\", \"\"]\n"     // Trailing
+        "[\"\", \"\", \"x\"]\n"      // Leading consecutive separators
+        "[\"x\", \"\", \"\"]\n"      // Trailing consecutive separators
+        "[\"a\", \"bb\", \"ccc\"]\n" // Multiple elements
+        "[\"\", \"x\", \"\"]\n"      // Self-overlapping separators 1
+        "[\"x\", \"x.\"]\n"          // Self-overlapping separators 2
+        ,
+        setup.print_output()) << setup.print_output();
+  }
+
+  // Verify usage errors are detected.
+  std::vector<std::string> bad_usage_examples = {
+    // Number of arguments.
+    R"gn(string_split())gn",
+    R"gn(string_split("a", "b", "c"))gn",
+
+    // Argument types.
+    R"gn(string_split(1))gn",
+    R"gn(string_split(["oops"]))gn",
+    R"gn(string_split("kk", 1))gn",
+    R"gn(string_split("kk", ["oops"]))gn",
+
+    // Empty separator argument.
+    R"gn(string_split("kk", ""))gn",
+  };
+  for (const auto& bad_usage_example : bad_usage_examples) {
+    TestParseInput input(bad_usage_example);
+    ASSERT_FALSE(input.has_error());
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    ASSERT_TRUE(err.has_error()) << bad_usage_example;
+  }
+}
+
+TEST(Functions, DeclareArgs) {
+  TestWithScope setup;
+  Err err;
+
+  // It is not legal to read the value of an argument declared in a
+  // declare_args() from inside the call, but outside the call and in
+  // a separate call should work.
+
+  TestParseInput reading_from_same_call(R"(
+      declare_args() {
+        foo = true
+        bar = foo
+      })");
+  reading_from_same_call.parsed()->Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+
+  TestParseInput reading_from_outside_call(R"(
+      declare_args() {
+        foo = true
+      }
+
+      bar = foo
+      assert(bar)
+      )");
+  err = Err();
+  reading_from_outside_call.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error());
+
+  TestParseInput reading_from_different_call(R"(
+      declare_args() {
+        foo = true
+      }
+
+      declare_args() {
+        bar = foo
+      }
+
+      assert(bar)
+      )");
+  err = Err();
+  TestWithScope setup2;
+  reading_from_different_call.parsed()->Execute(setup2.scope(), &err);
+  ASSERT_FALSE(err.has_error());
+}
+
+TEST(Functions, NotNeeded) {
+  TestWithScope setup;
+
+  TestParseInput input("not_needed({ a = 1 }, \"*\")");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error())
+      << err.message() << err.location().Describe(true);
+}
diff --git a/src/gn/general_tool.cc b/src/gn/general_tool.cc
new file mode 100644 (file)
index 0000000..fc8878d
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/general_tool.h"
+#include "gn/target.h"
+
+const char* GeneralTool::kGeneralToolStamp = "stamp";
+const char* GeneralTool::kGeneralToolCopy = "copy";
+const char* GeneralTool::kGeneralToolCopyBundleData = "copy_bundle_data";
+const char* GeneralTool::kGeneralToolCompileXCAssets = "compile_xcassets";
+const char* GeneralTool::kGeneralToolAction = "action";
+
+GeneralTool::GeneralTool(const char* n) : Tool(n) {
+  CHECK(ValidateName(n));
+}
+
+GeneralTool::~GeneralTool() = default;
+
+GeneralTool* GeneralTool::AsGeneral() {
+  return this;
+}
+const GeneralTool* GeneralTool::AsGeneral() const {
+  return this;
+}
+
+bool GeneralTool::ValidateName(const char* name) const {
+  return name == kGeneralToolStamp || name == kGeneralToolCopy ||
+         name == kGeneralToolCopyBundleData ||
+         name == kGeneralToolCompileXCAssets || name == kGeneralToolAction;
+}
+
+void GeneralTool::SetComplete() {
+  SetToolComplete();
+}
+
+bool GeneralTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+  // Initialize default vars.
+  return Tool::InitTool(scope, toolchain, err);
+}
+
+bool GeneralTool::ValidateSubstitution(const Substitution* sub_type) const {
+  if (name_ == kGeneralToolStamp || name_ == kGeneralToolAction)
+    return IsValidToolSubstitution(sub_type);
+  else if (name_ == kGeneralToolCopy || name_ == kGeneralToolCopyBundleData)
+    return IsValidCopySubstitution(sub_type);
+  else if (name_ == kGeneralToolCompileXCAssets)
+    return IsValidCompileXCassetsSubstitution(sub_type);
+  NOTREACHED();
+  return false;
+}
diff --git a/src/gn/general_tool.h b/src/gn/general_tool.h
new file mode 100644 (file)
index 0000000..eb16b2c
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_GENERAL_TOOL_H_
+#define TOOLS_GN_GENERAL_TOOL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/tool.h"
+
+class GeneralTool : public Tool {
+ public:
+  // General tools
+  static const char* kGeneralToolStamp;
+  static const char* kGeneralToolCopy;
+  static const char* kGeneralToolAction;
+
+  // Platform-specific tools
+  static const char* kGeneralToolCopyBundleData;
+  static const char* kGeneralToolCompileXCAssets;
+
+  GeneralTool(const char* n);
+  ~GeneralTool();
+
+  // Manual RTTI and required functions ---------------------------------------
+
+  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+  bool ValidateName(const char* name) const override;
+  void SetComplete() override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
+
+  GeneralTool* AsGeneral() override;
+  const GeneralTool* AsGeneral() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GeneralTool);
+};
+
+#endif  // TOOLS_GN_GENERAL_TOOL_H_
diff --git a/src/gn/generated_file_target_generator.cc b/src/gn/generated_file_target_generator.cc
new file mode 100644 (file)
index 0000000..2506f19
--- /dev/null
@@ -0,0 +1,165 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/generated_file_target_generator.h"
+
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+
+GeneratedFileTargetGenerator::GeneratedFileTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Target::OutputType type,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err), output_type_(type) {}
+
+GeneratedFileTargetGenerator::~GeneratedFileTargetGenerator() = default;
+
+void GeneratedFileTargetGenerator::DoRun() {
+  target_->set_output_type(output_type_);
+
+  if (!FillOutputs(false))
+    return;
+  if (target_->action_values().outputs().list().size() != 1) {
+    *err_ = Err(
+        function_call_, "generated_file target must have exactly one output.",
+        "You must specify exactly one value in the \"outputs\" array for the "
+        "destination of the write\n(see \"gn help generated_file\").");
+    return;
+  }
+
+  if (!FillContents())
+    return;
+  if (!FillDataKeys())
+    return;
+
+  // One of data and data_keys should be defined.
+  if (!contents_defined_ && !data_keys_defined_) {
+    *err_ = Err(
+        function_call_, "Either contents or data_keys should be set.",
+        "The generated_file target requires either the \"contents\" variable "
+        "or the \"data_keys\" variable be set. See \"gn help "
+        "generated_file\".");
+    return;
+  }
+
+  if (!FillRebase())
+    return;
+  if (!FillWalkKeys())
+    return;
+
+  if (!FillOutputConversion())
+    return;
+}
+
+bool GeneratedFileTargetGenerator::FillContents() {
+  const Value* value = scope_->GetValue(variables::kWriteValueContents, true);
+  if (!value)
+    return true;
+  target_->set_contents(*value);
+  contents_defined_ = true;
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::IsMetadataCollectionTarget(
+    const std::string_view& variable,
+    const ParseNode* origin) {
+  if (contents_defined_) {
+    *err_ =
+        Err(origin, std::string(variable) + " won't be used.",
+            "\"contents\" is defined on this target, and so setting " +
+                std::string(variable) +
+                " will have no effect as no metadata collection will occur.");
+    return false;
+  }
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillOutputConversion() {
+  const Value* value =
+      scope_->GetValue(variables::kWriteOutputConversion, true);
+  if (!value) {
+    target_->set_output_conversion(Value(function_call_, ""));
+    return true;
+  }
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  // Otherwise, the value itself will be checked when the conversion is done.
+  target_->set_output_conversion(*value);
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillRebase() {
+  const Value* value = scope_->GetValue(variables::kRebase, true);
+  if (!value)
+    return true;
+  if (!IsMetadataCollectionTarget(variables::kRebase, value->origin()))
+    return false;
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  if (value->string_value().empty())
+    return true;  // Treat empty string as the default and do nothing.
+
+  const BuildSettings* build_settings = scope_->settings()->build_settings();
+  SourceDir dir = scope_->GetSourceDir().ResolveRelativeDir(
+      *value, err_, build_settings->root_path_utf8());
+  if (err_->has_error())
+    return false;
+
+  target_->set_rebase(dir);
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillDataKeys() {
+  const Value* value = scope_->GetValue(variables::kDataKeys, true);
+  if (!value)
+    return true;
+  if (!IsMetadataCollectionTarget(variables::kDataKeys, value->origin()))
+    return false;
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  for (const Value& v : value->list_value()) {
+    // Keys must be strings.
+    if (!v.VerifyTypeIs(Value::STRING, err_))
+      return false;
+    target_->data_keys().push_back(v.string_value());
+  }
+
+  data_keys_defined_ = true;
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillWalkKeys() {
+  const Value* value = scope_->GetValue(variables::kWalkKeys, true);
+  // If we define this and contents, that's an error.
+  if (value &&
+      !IsMetadataCollectionTarget(variables::kWalkKeys, value->origin()))
+    return false;
+
+  // If we don't define it, we want the default value which is a list
+  // containing the empty string.
+  if (!value) {
+    target_->walk_keys().push_back("");
+    return true;
+  }
+
+  // Otherwise, pull and validate the specified value.
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+  for (const Value& v : value->list_value()) {
+    // Keys must be strings.
+    if (!v.VerifyTypeIs(Value::STRING, err_))
+      return false;
+    target_->walk_keys().push_back(v.string_value());
+  }
+  return true;
+}
diff --git a/src/gn/generated_file_target_generator.h b/src/gn/generated_file_target_generator.h
new file mode 100644 (file)
index 0000000..dcc4814
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_GENERATED_FILE_TARGET_GENERATOR_H_
+#define TOOLS_GN_GENERATED_FILE_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target.h"
+#include "gn/target_generator.h"
+
+// Collects and writes specified data.
+class GeneratedFileTargetGenerator : public TargetGenerator {
+ public:
+  GeneratedFileTargetGenerator(Target* target,
+                               Scope* scope,
+                               const FunctionCallNode* function_call,
+                               Target::OutputType type,
+                               Err* err);
+  ~GeneratedFileTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillGeneratedFileOutput();
+  bool FillOutputConversion();
+  bool FillContents();
+  bool FillDataKeys();
+  bool FillWalkKeys();
+  bool FillRebase();
+
+  // Returns false if `contents` is defined (i.e. if this target was provided
+  // with explicit contents to write). Returns false otherwise, indicating that
+  // it is okay to set metadata collection variables on this target.
+  //
+  // Should be called before FillContents().
+  bool IsMetadataCollectionTarget(const std::string_view& variable,
+                                  const ParseNode* origin);
+
+  bool contents_defined_ = false;
+  bool data_keys_defined_ = false;
+
+  Target::OutputType output_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(GeneratedFileTargetGenerator);
+};
+
+#endif  // TOOLS_GN_GENERATED_FILE_TARGET_GENERATOR_H_
diff --git a/src/gn/gn_main.cc b/src/gn/gn_main.cc
new file mode 100644 (file)
index 0000000..e839b32
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/commands.h"
+#include "gn/err.h"
+#include "gn/location.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "util/build_config.h"
+#include "util/msg_loop.h"
+#include "util/sys_info.h"
+
+#include "last_commit_position.h"
+
+namespace {
+
+std::vector<std::string> GetArgs(const base::CommandLine& cmdline) {
+  base::CommandLine::StringVector in_args = cmdline.GetArgs();
+#if defined(OS_WIN)
+  std::vector<std::string> out_args;
+  for (const auto& arg : in_args)
+    out_args.push_back(base::UTF16ToUTF8(arg));
+  return out_args;
+#else
+  return in_args;
+#endif
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+#if defined(OS_WIN)
+  base::CommandLine::set_slash_is_not_a_switch();
+#endif
+  base::CommandLine::Init(argc, argv);
+
+  const base::CommandLine& cmdline = *base::CommandLine::ForCurrentProcess();
+  std::vector<std::string> args = GetArgs(cmdline);
+
+  std::string command;
+  if (cmdline.HasSwitch("help") || cmdline.HasSwitch("h")) {
+    // Make "-h" and "--help" default to help command.
+    command = commands::kHelp;
+  } else if (cmdline.HasSwitch(switches::kVersion)) {
+    // Make "--version" print the version and exit.
+    OutputString(std::string(LAST_COMMIT_POSITION) + "\n");
+    exit(0);
+  } else if (args.empty()) {
+    // No command, print error and exit.
+    Err(Location(), "No command specified.",
+        "Most commonly you want \"gn gen <out_dir>\" to make a build dir.\n"
+        "Or try \"gn help\" for more commands.")
+        .PrintToStdout();
+    return 1;
+  } else {
+    command = args[0];
+    args.erase(args.begin());
+  }
+
+  const commands::CommandInfoMap& command_map = commands::GetCommands();
+  commands::CommandInfoMap::const_iterator found_command =
+      command_map.find(command);
+
+  int retval;
+  if (found_command != command_map.end()) {
+    MsgLoop msg_loop;
+    retval = found_command->second.runner(args);
+  } else {
+    Err(Location(), "Command \"" + command + "\" unknown.").PrintToStdout();
+    OutputString(
+        "Available commands (type \"gn help <command>\" for more details):\n");
+    for (const auto& cmd : commands::GetCommands())
+      PrintShortHelp(cmd.second.help_short);
+
+    retval = 1;
+  }
+
+  exit(retval);  // Don't free memory, it can be really slow!
+}
diff --git a/src/gn/group_target_generator.cc b/src/gn/group_target_generator.cc
new file mode 100644 (file)
index 0000000..2b91066
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/group_target_generator.h"
+
+#include "gn/target.h"
+#include "gn/variables.h"
+
+GroupTargetGenerator::GroupTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err) {}
+
+GroupTargetGenerator::~GroupTargetGenerator() = default;
+
+void GroupTargetGenerator::DoRun() {
+  target_->set_output_type(Target::GROUP);
+  // Groups only have the default types filled in by the target generator
+  // base class.
+}
diff --git a/src/gn/group_target_generator.h b/src/gn/group_target_generator.h
new file mode 100644 (file)
index 0000000..9845929
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_GROUP_TARGET_GENERATOR_H_
+#define TOOLS_GN_GROUP_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target_generator.h"
+
+// Populates a Target with the values for a group rule.
+class GroupTargetGenerator : public TargetGenerator {
+ public:
+  GroupTargetGenerator(Target* target,
+                       Scope* scope,
+                       const FunctionCallNode* function_call,
+                       Err* err);
+  ~GroupTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GroupTargetGenerator);
+};
+
+#endif  // TOOLS_GN_GROUP_TARGET_GENERATOR_H_
diff --git a/src/gn/hash_table_base.h b/src/gn/hash_table_base.h
new file mode 100644 (file)
index 0000000..c26b514
--- /dev/null
@@ -0,0 +1,503 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_HASH_TABLE_BASE_H_
+#define TOOLS_GN_HASH_TABLE_BASE_H_
+
+#include "base/compiler_specific.h"
+
+#include <stdlib.h>
+#include <type_traits>
+#include <utility>
+
+// IMPORTANT DISCLAIMER:
+//
+// THIS IS *NOT* A GENERAL PURPOSE HASH TABLE TEMPLATE. INSTEAD, IT CAN
+// CAN BE USED AS THE BASIS FOR VERY HIGH SPEED AND COMPACT HASH TABLES
+// THAT OBEY VERY STRICT CONDITIONS DESCRIBED BELOW.
+//
+// DO NOT USE THIS UNLESS YOU HAVE A GOOD REASON, I.E. THAT PROFILING
+// SHOWS THERE *IS* AN OVERALL BENEFIT TO DO SO. FOR MOST CASES,
+// std::set<>, std::unordered_set<> and base::flat_set<> ARE PERFECTLY
+// FINE AND SHOULD BE PREFERRED.
+//
+//
+// That being said, this implementation uses a completely typical
+// open-addressing scheme with a buckets array size which is always
+// a power of 2, instead of a prime number. Experience shows this is
+// not detrimental to performance as long as you have a sufficiently
+// good hash function (which is the case of all C++ standard libraries
+// these days for strings and pointers).
+//
+// The reason it is so fast is due to its compactness and the very
+// simple but tight code for a typical lookup / insert / deletion
+// operation.
+//
+// The |buckets_| field holds a pointer to an array of NODE_TYPE
+// instances, called nodes. Each node represents one of the following:
+// a free entry in the table, an inserted item, or a tombstone marking
+// the location of a previously deleted item. Tombstones are only
+// used if the hash table instantiation requires deletion support
+// (see the is_tombstone() description below).
+//
+// The template requires that Node be a type with the following traits:
+//
+//  - It *must* be a trivial type, that is zero-initialized.
+//
+//  - It provides an is_null() const method, which should return true
+//    iff the corresponding node matches a 'free' entry in the hash
+//    table, i.e. one that has not been assigned to an item, or
+//    to a deletion tombstone (see below).
+//
+//    Of course, a default (zeroed) value, should always return true.
+//
+//  - It provides an is_tombstone() const method, which should return
+//    return true iff the corresponding node indicates a previously
+//    deleted item.
+//
+//    Note that if your hash table does not need deletion support,
+//    it is highly recommended to make this a static constexpr method
+//    that always return false. Doing so will optimize the lookup loop
+//    automatically!
+//
+//  - It must provide an is_valid() method, that simply returns
+//    (!is_null() && !is_tombstone()). This is a convenience for this
+//    template, but also for the derived class that will extend it
+//    (more on this below, in the usage description).
+//
+// Note that because Node instances are trivial, std::unique_ptr<>
+// cannot be used in them. Item lifecycle must this be managed
+// explicitly by a derived class of the template instantiation
+// instead.
+//
+// Lookup, insertion and deletion are performed in ways that
+// are *very* different from standard containers, and the reason
+// is, unsurprisingly, performance.
+//
+// Use NodeLookup() to look for an existing item in the hash table.
+// This takes the item's hash value, and a templated callable to
+// compare a node against the search key. This scheme allows
+// heterogeneous lookups, and keeps the node type details
+// out of this header. Any recent C++ optimizer will generate
+// very tight machine code for this call.
+//
+// The NodeLookup() function always returns a non-null and
+// mutable |node| pointer. If |node->is_valid()| is true,
+// then the item was found, and |node| points to it.
+//
+// Otherwise, |node| corresponds to a location that may be
+// used for insertion. To do so, the caller should update the
+// content of |node| appropriately (e.g. writing a pointer and/or
+// hash value to it), then call UpdateAfterInsertion(), which
+// may eventually grow the table and rehash nodes in it.
+//
+// To delete an item, call NodeLookup() first to verify that
+// the item is present, then write a tombstone value to |node|,
+// then call UpdateAfterDeletion().
+//
+// Note that what the tombstone value is doesn't matter to this
+// header, as long as |node->is_tombstone()| returns true for
+// it.
+//
+// Here's an example of a concrete implementation that stores
+// a hash value and an owning pointer in each node:
+//
+//     struct MyFooNode {
+//       size_t hash;
+//       Foo*   foo;
+//
+//       bool is_null() const { return !foo; }
+//       bool is_tombstone() const { return foo == &kTombstone; }
+//       bool is_valid() const { return !is_null() && !is_tombstone(); }
+//
+//       static const Foo kTombstone;
+//     };
+//
+//    class MyFooSet : public HashTableBase<MyFoodNode> {
+//    public:
+//      using BaseType = HashTableBase<MyFooNode>;
+//      using Node = BaseType::Node;
+//
+//      ~MyFooSet() {
+//        // Destroy all items in the set.
+//        // Note that the iterator only parses over valid items.
+//        for (Node* node : *this) {
+//          delete node->foo;
+//        }
+//      }
+//
+//      // Returns true iff this set contains |key|.
+//      bool contains(const Foo& key) const {
+//        Node* node = BaseType::NodeLookup(
+//            MakeHash(key),
+//            [&](const Node* node) { return node->foo == key; });
+//
+//        return node->is_valid();
+//      }
+//
+//      // Try to add |key| to the set. Return true if the insertion
+//      // was successful, or false if the item was already in the set.
+//      bool add(const Foo& key) {
+//        size_t hash = MakeHash(key);
+//        Node* node = NodeLookup(
+//            hash,
+//            [&](const Node* node) { return node->foo == key; });
+//
+//        if (node->is_valid()) {
+//          // Already in the set.
+//          return false;
+//        }
+//
+//        // Add it.
+//        node->hash = hash;
+//        node->foo  = new Foo(key);
+//        UpdateAfterInsert();
+//        return true;
+//      }
+//
+//      // Try to remove |key| from the set. Return true if the
+//      // item was already in it, false otherwise.
+//      bool erase(const Foo& key) {
+//        Node* node = BaseType::NodeLookup(
+//            MakeHash(key),
+//            [&](const Node* node) { return node->foo == key; });
+//
+//        if (!node->is_valid()) {
+//          // Not in the set.
+//          return false;
+//        }
+//
+//        delete node->foo;
+//        node->foo = Node::kTombstone;
+//        UpdateAfterDeletion().
+//      }
+//
+//      static size_t MakeHash(const Foo& foo) {
+//        return std::hash<Foo>()();
+//      }
+//    };
+//
+// For more concrete examples, see the implementation of
+// StringAtom or UniqueVector<>
+//
+template <typename NODE_TYPE>
+class HashTableBase {
+ public:
+  using Node = NODE_TYPE;
+
+  static_assert(std::is_trivial<NODE_TYPE>::value,
+                "KEY_TYPE should be a trivial type!");
+
+  static_assert(std::is_standard_layout<NODE_TYPE>::value,
+                "KEY_TYPE should be a standard layout type!");
+
+  // Default constructor.
+  HashTableBase() = default;
+
+  // Destructor. This only deals with the |buckets_| array itself.
+  ~HashTableBase() {
+    if (buckets_ != buckets0_)
+      free(buckets_);
+  }
+
+  // Copy and move operations. These only operate on the |buckets_| array
+  // so any owned pointer inside nodes should be handled by custom
+  // constructors and operators in the derived class, if needed.
+  HashTableBase(const HashTableBase& other)
+      : count_(other.count_), size_(other.size_) {
+    if (other.buckets_ != other.buckets0_) {
+      // NOTE: using malloc() here to clarify that no object construction
+      // should occur here.
+      buckets_ = reinterpret_cast<Node*>(malloc(other.size_ * sizeof(Node)));
+    }
+    memcpy(buckets_, other.buckets_, other.size_ * sizeof(Node));
+  }
+
+  HashTableBase& operator=(const HashTableBase& other) {
+    if (this != &other) {
+      this->~HashTableBase();
+      new (this) HashTableBase(other);
+    }
+    return *this;
+  }
+
+  HashTableBase(HashTableBase&& other) noexcept
+      : count_(other.count_), size_(other.size_), buckets_(other.buckets_) {
+    if (buckets_ == other.buckets0_) {
+      buckets0_[0] = other.buckets0_[0];
+      buckets_ = buckets0_;
+    } else {
+      other.buckets_ = other.buckets0_;
+    }
+    other.NodeClear();
+  }
+
+  HashTableBase& operator=(HashTableBase&& other) noexcept {
+    if (this != &other) {
+      this->~HashTableBase();
+      new (this) HashTableBase(std::move(other));
+    }
+    return *this;
+  }
+
+  // Return true iff the table is empty.
+  bool empty() const { return count_ == 0; }
+
+  // Return the number of keys in the set.
+  size_t size() const { return count_; }
+
+ protected:
+  // The following should only be called by derived classes that
+  // extend this template class, and are not available to their
+  // clients. This forces the derived class to implement lookup
+  // insertion and deletion with sane APIs instead.
+
+  // Iteration support note:
+  //
+  // Derived classes, if they wish to support iteration, should provide their
+  // own iterator/const_iterator/begin()/end() types and methods, possibly as
+  // simple wrappers around the NodeIterator/NodeBegin/NodeEnd below.
+  //
+  // The ValidNodesRange() method also returns a object that has begin() and
+  // end() methods to be used in for-range loops as in:
+  //
+  //    for (Node& node : my_table.ValidNodesRange()) { ... }
+  //
+  struct NodeIterator {
+    Node& operator*() { return *node_; }
+    const Node& operator*() const { return *node_; }
+
+    Node* operator->() { return node_; }
+    const Node* operator->() const { return node_; }
+
+    bool operator==(const NodeIterator& other) const {
+      return node_ == other.node_;
+    }
+
+    bool operator!=(const NodeIterator& other) const {
+      return node_ != other.node_;
+    }
+
+    // pre-increment
+    NodeIterator& operator++() {
+      node_++;
+      while (node_ < node_limit_ && !node_->is_valid())
+        node_++;
+
+      return *this;
+    }
+
+    // post-increment
+    NodeIterator operator++(int) {
+      NodeIterator result = *this;
+      ++(*this);
+      return result;
+    }
+
+    Node* node_ = nullptr;
+    Node* node_limit_ = nullptr;
+  };
+
+  // NOTE: This is intentionally not public to avoid exposing
+  // them in derived classes by mistake. If a derived class
+  // wants to support iteration, it provide its own begin() and end()
+  // methods, possibly using NodeBegin() and NodeEnd() below to
+  // implement them.
+  NodeIterator begin() { return NodeBegin(); }
+  NodeIterator end() { return NodeEnd(); }
+
+  // Providing methods named NodeBegin() and NodeEnd(), instead of
+  // just using begin() and end() is a convenience to derived classes
+  // that need to provide their own begin() and end() method, e.g.
+  // consider this class:
+  //
+  //  struct FooNode {
+  //     size_t hash;
+  //     Foo*   foo_ptr;
+  //     ...
+  //  };
+  //
+  //  class FooTable : public HashTableBase<FooNode> {
+  //  public:
+  //     ...
+  //
+  //     // Iterators point to Foo instances, not table nodes.
+  //     struct ConstIterator {
+  //       const Foo* operator->() { return iter_->foo_ptr; }
+  //       const Foo& operator->() { return *(iter_->foo_ptr); }
+  //       ...
+  //       NodeIterator iter_;
+  //     };
+  //
+  // and compare two ways to implement its begin() method:
+  //
+  //    Foo::ConstIterator Foo::begin() const {
+  //      return {
+  //        reinterpret_cast<const HashTableBase<FooNode> *>(this)->begin()
+  //      };
+  //    };
+  //
+  // with:
+  //
+  //    Foo::ConstIterator Foo::begin() const {
+  //      return { NodeBegin(); }
+  //    }
+  //
+  NodeIterator NodeBegin() const {
+    Node* node = buckets_;
+    Node* limit = node + size_;
+    while (node < limit && !node->is_valid())
+      node++;
+
+    return {node, limit};
+  }
+
+  NodeIterator NodeEnd() const {
+    Node* limit = buckets_ + size_;
+    return {limit, limit};
+  }
+
+  // ValidNodeRange() allows a derived-class to use range-based  loops
+  // over valid nodes, even if they have defined their own begin() and
+  // end() methods, e.g. following the same class design as in the
+  // above comment:
+  //
+  //   FooTable::~FooTable() {
+  //     for (const FooNode& node : ValidNodesRange()) {
+  //       delete node->foo_ptr;
+  //     }
+  //   }
+  //
+  // which is a little bit clearer than the (valid) alternative:
+  //
+  //   FooTable::~FooTable() {
+  //     for (Foo& foo : *this) {
+  //       delete &foo;  // WHAT!?
+  //     }
+  //   }
+  //
+  struct NodeIteratorPair {
+    NodeIterator begin() { return begin_; }
+    NodeIterator end() { return end_; }
+
+    NodeIterator begin_;
+    NodeIterator end_;
+  };
+
+  NodeIteratorPair ValidNodesRange() const { return {NodeBegin(), NodeEnd()}; }
+
+  // Clear the nodes table completely.
+  void NodeClear() {
+    if (buckets_ != buckets0_)
+      free(buckets_);
+
+    count_ = 0;
+    size_ = 1;
+    buckets_ = buckets0_;
+    buckets0_[0] = Node{};
+  }
+
+  // Lookup for a node in the hash table that matches the |node_equal|
+  // predicate, which takes a const Node* pointer argument, and returns
+  // true iff this corresponds to a lookup match.
+  //
+  // IMPORTANT: |node_equal| may or may not check the node hash value,
+  // the choice is left to the implementation.
+  //
+  // Returns a non-null *mutable* node pointer. |node->is_valid()| will
+  // be true for matches, and false for misses.
+  //
+  // NOTE: In case of a miss, this will return the location of the first
+  // tombstone encountered during probing, if any, or the first free entry
+  // otherwise. This keeps the table consistent in case the node is overwritten
+  // by the caller in a following insert operation.
+  template <typename NODE_EQUAL>
+  Node* NodeLookup(size_t hash, NODE_EQUAL node_equal) const {
+    size_t mask = size_ - 1;
+    size_t index = hash & mask;
+    Node* tombstone = nullptr;  // First tombstone node found, if any.
+    for (;;) {
+      Node* node = buckets_ + index;
+      if (node->is_null()) {
+        return tombstone ? tombstone : node;
+      }
+      if (node->is_tombstone()) {
+        if (!tombstone)
+          tombstone = node;
+      } else if (node_equal(node)) {
+        return node;
+      }
+      index = (index + 1) & mask;
+    }
+  }
+
+  // Call this method after updating the content of the |node| pointer
+  // returned by an unsuccessful NodeLookup(). Return true to indicate that
+  // the table size changed, and that existing iterators were invalidated.
+  bool UpdateAfterInsert() {
+    count_ += 1;
+    if (UNLIKELY(count_ * 4 >= size_ * 3)) {
+      GrowBuckets();
+      return true;
+    }
+    return false;
+  }
+
+  // Call this method after updating the content of the |node| value
+  // returned a by successful NodeLookup, to the tombstone value, if any.
+  // Return true to indicate a table size change, ie. that existing
+  // iterators where invalidated.
+  bool UpdateAfterRemoval() {
+    count_ -= 1;
+    // For now don't support shrinking since this is not useful for GN.
+    return false;
+  }
+
+ private:
+#if defined(__GNUC__) || defined(__clang__)
+  [[gnu::noinline]]
+#endif
+  void
+  GrowBuckets() {
+    size_t size = size_;
+    size_t new_size = (size == 1) ? 8 : size * 2;
+    size_t new_mask = new_size - 1;
+
+    // NOTE: Using calloc() since no object constructiopn can or should take
+    // place here.
+    Node* new_buckets = reinterpret_cast<Node*>(calloc(new_size, sizeof(Node)));
+
+    for (size_t src_index = 0; src_index < size; ++src_index) {
+      const Node* node = &buckets_[src_index];
+      if (!node->is_valid())
+        continue;
+      size_t dst_index = node->hash_value() & new_mask;
+      for (;;) {
+        Node* node2 = new_buckets + dst_index;
+        if (node2->is_null()) {
+          *node2 = *node;
+          break;
+        }
+        dst_index = (dst_index + 1) & new_mask;
+      }
+    }
+
+    if (buckets_ != buckets0_)
+      free(buckets_);
+
+    buckets_ = new_buckets;
+    buckets0_[0] = Node{};
+    size_ = new_size;
+  }
+
+  // NOTE: The reason for default-initializing |buckets_| to a storage slot
+  // inside the object is to ensure the value is never null. This removes one
+  // nullptr check from each NodeLookup() instantiation.
+  size_t count_ = 0;
+  size_t size_ = 1;
+  Node* buckets_ = buckets0_;
+  Node buckets0_[1] = {{}};
+};
+
+#endif  // TOOLS_GN_HASH_TABLE_BASE_H_
diff --git a/src/gn/hash_table_base_unittest.cc b/src/gn/hash_table_base_unittest.cc
new file mode 100644 (file)
index 0000000..d6324cf
--- /dev/null
@@ -0,0 +1,356 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hash_table_base.h"
+
+#include "util/test/test.h"
+
+#include <algorithm>
+#include <vector>
+
+// This unit-test is also used to illustrate how to use HashTableBase<>
+// in a concrete way. Here, each node is a simple pointer to a Int class
+// that wraps a simple integer, but also keeps tracks of
+// construction/destruction steps in global counters. This is used by the
+// test to verify that operations like copies or moves do not miss or
+// create allocations/deallocations.
+//
+// Because the derived table HashTableTest owns all pointer objects, it
+// needs to manually create/deallocate them in its destructor, copy/move
+// constructors and operators, as well as insert()/erase()/clear() methods.
+//
+// Finally, iteration support is provided through const_iterator and
+// begin(), end() methods, which are enough for range-based for loops.
+//
+
+// A simple int wrapper class that can also count creation/destruction.
+class Int {
+ public:
+  explicit Int(int x) : x_(x) { creation_counter++; }
+
+  Int(const Int& other) : x_(other.x_) { creation_counter++; }
+
+  ~Int() { destruction_counter++; }
+
+  int& x() { return x_; }
+  const int& x() const { return x_; }
+
+  size_t hash() const { return static_cast<size_t>(x_); }
+
+  static void ResetCounters() {
+    creation_counter = 0;
+    destruction_counter = 0;
+  }
+
+  int x_;
+
+  static size_t creation_counter;
+  static size_t destruction_counter;
+};
+
+size_t Int::creation_counter;
+size_t Int::destruction_counter;
+
+// A HashTableBase<> node type that contains a simple pointer to a Int value.
+struct TestHashNode {
+  Int* int_ptr;
+
+  bool is_null() const { return !int_ptr; }
+  bool is_tombstone() const { return int_ptr == &kTombstone; }
+  bool is_valid() const { return !is_null() && !is_tombstone(); }
+  size_t hash_value() const { return int_ptr->hash(); }
+
+  static Int kTombstone;
+};
+
+// Value is irrelevant since only its address is used.
+Int TestHashNode::kTombstone(-1);
+
+// TestHashTable derives a HashTableBase<> instantiation to demonstrate
+// full uses of the template. This includes:
+//
+//  - Storing a pointer in each node, and managing ownership of pointed
+//    objects explicitly in the destructor, copy/move constructor and
+//    operator, as well as insert() and erase() methods.
+//
+//  - The internal pointed objects are Int instance, but the TestHashTable
+//    API masks that entirely, instead implementing a simple set of integers,
+//    including iteration support.
+//
+//  Note that placing the integers directly in the nodes would be much easier,
+//  but would not allow demonstrating how to manage ownership in thedestructor
+class TestHashTable : public HashTableBase<TestHashNode> {
+ public:
+  using BaseType = HashTableBase<TestHashNode>;
+  using Node = BaseType::Node;
+
+  static_assert(std::is_same<Node, TestHashNode>::value,
+                "HashTableBase<>::Node is not defined properly!");
+
+  BaseType& asBaseType() { return *this; }
+  const BaseType& asBaseType() const { return *this; }
+
+  TestHashTable() = default;
+
+  // IMPORTANT NOTE: Because the table contains bare owning pointers, we
+  // have to use explicit copy and move constructor/operators for things
+  // to work as expected. This is yet another reason why HashTableBase<>
+  // should only be used with care (preferably with non-owning pointers).
+  //
+  TestHashTable(const TestHashTable& other) : BaseType(other) {
+    // Only node (i.e. pointers) are copied by the base type.
+    for (Node& node : ValidNodesRange()) {
+      node.int_ptr = new Int(*node.int_ptr);
+    }
+  }
+
+  TestHashTable& operator=(const TestHashTable& other) {
+    if (this != &other) {
+      this->~TestHashTable();
+      new (this) TestHashTable(other);
+    }
+    return *this;
+  }
+
+  TestHashTable(TestHashTable&& other) noexcept : BaseType(std::move(other)) {}
+  TestHashTable& operator=(TestHashTable&& other) noexcept {
+    if (this != &other) {
+      this->~TestHashTable();
+      new (this) TestHashTable(std::move(other));
+    }
+    return *this;
+  }
+
+  ~TestHashTable() {
+    // Discard all valid Int pointers in the hash table.
+    for (Node& node : ValidNodesRange())
+      delete node.int_ptr;
+  }
+
+  // Return true iff the table contains |x|.
+  bool contains(int x) const {
+    size_t hash = static_cast<size_t>(x);
+    Node* node = NodeLookup(
+        hash, [&](const Node* node) { return node->int_ptr->x() == x; });
+    return node->is_valid();
+  }
+
+  // Try to insert |x| in the table. Returns true on success, or false if
+  // the value was already in it.
+  bool insert(int x) {
+    size_t hash = static_cast<size_t>(x);
+    Node* node = NodeLookup(
+        hash, [&](const Node* node) { return node->int_ptr->x() == x; });
+    if (node->is_valid())
+      return false;
+
+    node->int_ptr = new Int(x);
+    UpdateAfterInsert();
+    return true;
+  }
+
+  // Try to remove |x| from the table. Return true on success, or false
+  // if the value was not in it.
+  bool erase(int x) {
+    size_t hash = static_cast<size_t>(x);
+    Node* node = NodeLookup(
+        hash, [&](const Node* node) { return node->int_ptr->x() == x; });
+    if (!node->is_valid())
+      return false;
+
+    delete node->int_ptr;
+    node->int_ptr = &TestHashNode::kTombstone;
+    UpdateAfterRemoval();
+    return true;
+  }
+
+  // Remove all items
+  void clear() {
+    // Remove all pointed objects, since NodeClear() will not do it.
+    for (Node& node : ValidNodesRange())
+      delete node.int_ptr;
+
+    NodeClear();
+  }
+
+  // Define const_iterator that point to the integer value instead of the node
+  // of the Int instance to completely hide these from this class' API.
+  struct const_iterator : public BaseType::NodeIterator {
+    const int& operator*() const {
+      return (this->BaseType::NodeIterator::operator*()).int_ptr->x();
+    }
+    const int* operator->() const { return &(this->operator*()); }
+  };
+
+  const_iterator begin() const { return {BaseType::NodeBegin()}; }
+
+  const_iterator end() const { return {BaseType::NodeEnd()}; }
+};
+
+TEST(HashTableBaseTest, Construction) {
+  Int::ResetCounters();
+  {
+    TestHashTable table;
+    EXPECT_TRUE(table.empty());
+    EXPECT_EQ(table.size(), 0u);
+    EXPECT_EQ(table.begin(), table.end());
+  }
+
+  // No item was created or destroyed.
+  EXPECT_EQ(Int::creation_counter, 0u);
+  EXPECT_EQ(Int::destruction_counter, 0u);
+}
+
+TEST(HashTableBaseTest, InsertionsAndLookups) {
+  Int::ResetCounters();
+  {
+    TestHashTable table;
+    table.insert(1);
+    table.insert(5);
+    table.insert(7);
+
+    EXPECT_FALSE(table.empty());
+    EXPECT_EQ(table.size(), 3u);
+    EXPECT_NE(table.begin(), table.end());
+
+    EXPECT_EQ(Int::creation_counter, 3u);
+    EXPECT_EQ(Int::destruction_counter, 0u);
+
+    EXPECT_FALSE(table.contains(0));
+    EXPECT_TRUE(table.contains(1));
+    EXPECT_FALSE(table.contains(2));
+    EXPECT_FALSE(table.contains(3));
+    EXPECT_TRUE(table.contains(5));
+    EXPECT_FALSE(table.contains(6));
+    EXPECT_TRUE(table.contains(7));
+    EXPECT_FALSE(table.contains(8));
+  }
+
+  EXPECT_EQ(Int::creation_counter, 3u);
+  EXPECT_EQ(Int::destruction_counter, 3u);
+}
+
+TEST(HashTableBaseTest, CopyAssignment) {
+  Int::ResetCounters();
+  {
+    TestHashTable table;
+    table.insert(1);
+    table.insert(5);
+    table.insert(7);
+
+    EXPECT_FALSE(table.empty());
+    EXPECT_EQ(table.size(), 3u);
+
+    TestHashTable table2;
+    EXPECT_TRUE(table2.empty());
+    table2 = table;
+    EXPECT_FALSE(table2.empty());
+    EXPECT_EQ(table2.size(), 3u);
+    EXPECT_FALSE(table.empty());
+    EXPECT_EQ(table.size(), 3u);
+
+    EXPECT_EQ(Int::creation_counter, 6u);
+    EXPECT_EQ(Int::destruction_counter, 0u);
+
+    EXPECT_FALSE(table.contains(0));
+    EXPECT_TRUE(table.contains(1));
+    EXPECT_FALSE(table.contains(2));
+    EXPECT_FALSE(table.contains(3));
+    EXPECT_TRUE(table.contains(5));
+    EXPECT_FALSE(table.contains(6));
+    EXPECT_TRUE(table.contains(7));
+    EXPECT_FALSE(table.contains(8));
+
+    EXPECT_FALSE(table2.contains(0));
+    EXPECT_TRUE(table2.contains(1));
+    EXPECT_FALSE(table2.contains(2));
+    EXPECT_FALSE(table2.contains(3));
+    EXPECT_TRUE(table2.contains(5));
+    EXPECT_FALSE(table2.contains(6));
+    EXPECT_TRUE(table2.contains(7));
+    EXPECT_FALSE(table2.contains(8));
+  }
+
+  EXPECT_EQ(Int::creation_counter, 6u);
+  EXPECT_EQ(Int::destruction_counter, 6u);
+}
+
+TEST(HashTableBaseTest, MoveAssignment) {
+  Int::ResetCounters();
+  {
+    TestHashTable table;
+    table.insert(1);
+    table.insert(5);
+    table.insert(7);
+
+    EXPECT_FALSE(table.empty());
+    EXPECT_EQ(table.size(), 3u);
+
+    TestHashTable table2;
+    EXPECT_TRUE(table2.empty());
+    table2 = std::move(table);
+    EXPECT_FALSE(table2.empty());
+    EXPECT_EQ(table2.size(), 3u);
+    EXPECT_TRUE(table.empty());
+    EXPECT_EQ(table.size(), 0u);
+
+    EXPECT_EQ(Int::creation_counter, 3u);
+    EXPECT_EQ(Int::destruction_counter, 0u);
+
+    EXPECT_FALSE(table2.contains(0));
+    EXPECT_TRUE(table2.contains(1));
+    EXPECT_FALSE(table2.contains(2));
+    EXPECT_FALSE(table2.contains(3));
+    EXPECT_TRUE(table2.contains(5));
+    EXPECT_FALSE(table2.contains(6));
+    EXPECT_TRUE(table2.contains(7));
+    EXPECT_FALSE(table2.contains(8));
+  }
+
+  EXPECT_EQ(Int::creation_counter, 3u);
+  EXPECT_EQ(Int::destruction_counter, 3u);
+}
+
+TEST(HashTableBaseTest, Clear) {
+  Int::ResetCounters();
+  {
+    TestHashTable table;
+    table.insert(1);
+    table.insert(5);
+    table.insert(7);
+
+    EXPECT_FALSE(table.empty());
+    EXPECT_EQ(table.size(), 3u);
+
+    table.clear();
+    EXPECT_TRUE(table.empty());
+
+    EXPECT_EQ(Int::creation_counter, 3u);
+    EXPECT_EQ(Int::destruction_counter, 3u);
+  }
+
+  EXPECT_EQ(Int::creation_counter, 3u);
+  EXPECT_EQ(Int::destruction_counter, 3u);
+}
+
+TEST(HashTableBaseTest, Iteration) {
+  TestHashTable table;
+  table.insert(1);
+  table.insert(5);
+  table.insert(7);
+
+  EXPECT_FALSE(table.empty());
+  EXPECT_EQ(table.size(), 3u);
+
+  std::vector<int> values;
+
+  for (const int& x : table)
+    values.push_back(x);
+
+  std::sort(values.begin(), values.end());
+  EXPECT_EQ(values.size(), 3u);
+  EXPECT_EQ(values[0], 1);
+  EXPECT_EQ(values[1], 5);
+  EXPECT_EQ(values[2], 7);
+}
diff --git a/src/gn/header_checker.cc b/src/gn/header_checker.cc
new file mode 100644 (file)
index 0000000..212f581
--- /dev/null
@@ -0,0 +1,642 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/header_checker.h"
+
+#include <algorithm>
+
+#include "base/containers/queue.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/c_include_iterator.h"
+#include "gn/config.h"
+#include "gn/config_values_extractors.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+#include "util/worker_pool.h"
+
+namespace {
+
+struct PublicGeneratedPair {
+  PublicGeneratedPair() : is_public(false), is_generated(false) {}
+  bool is_public;
+  bool is_generated;
+};
+
+// This class makes InputFiles on the stack as it reads files to check. When
+// we throw an error, the Err indicates a locatin which has a pointer to
+// an InputFile that must persist as long as the Err does.
+//
+// To make this work, this function creates a clone of the InputFile managed
+// by the InputFileManager so the error can refer to something that
+// persists. This means that the current file contents will live as long as
+// the program, but this is OK since we're erroring out anyway.
+LocationRange CreatePersistentRange(const InputFile& input_file,
+                                    const LocationRange& range) {
+  InputFile* clone_input_file;
+  std::vector<Token>* tokens;              // Don't care about this.
+  std::unique_ptr<ParseNode>* parse_root;  // Don't care about this.
+
+  g_scheduler->input_file_manager()->AddDynamicInput(
+      input_file.name(), &clone_input_file, &tokens, &parse_root);
+  clone_input_file->SetContents(input_file.contents());
+
+  return LocationRange(
+      Location(clone_input_file, range.begin().line_number(),
+               range.begin().column_number(), -1 /* TODO(scottmg) */),
+      Location(clone_input_file, range.end().line_number(),
+               range.end().column_number(), -1 /* TODO(scottmg) */));
+}
+
+// Given a reverse dependency chain where the target chain[0]'s includes are
+// being used by chain[end] and not all deps are public, returns the string
+// describing the error.
+std::string GetDependencyChainPublicError(const HeaderChecker::Chain& chain) {
+  std::string ret =
+      "The target:\n  " +
+      chain[chain.size() - 1].target->label().GetUserVisibleName(false) +
+      "\nis including a file from the target:\n  " +
+      chain[0].target->label().GetUserVisibleName(false) + "\n";
+
+  // Invalid chains should always be 0 (no chain) or more than two
+  // (intermediate private dependencies). 1 and 2 are impossible because a
+  // target can always include headers from itself and its direct dependents.
+  DCHECK(chain.size() != 1 && chain.size() != 2);
+  if (chain.empty()) {
+    ret += "There is no dependency chain between these targets.";
+  } else {
+    // Indirect dependency chain, print the chain.
+    ret +=
+        "\nIt's usually best to depend directly on the destination target.\n"
+        "In some cases, the destination target is considered a subcomponent\n"
+        "of an intermediate target. In this case, the intermediate target\n"
+        "should depend publicly on the destination to forward the ability\n"
+        "to include headers.\n"
+        "\n"
+        "Dependency chain (there may also be others):\n";
+
+    for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
+      ret.append("  " + chain[i].target->label().GetUserVisibleName(false));
+      if (i != 0) {
+        // Identify private dependencies so the user can see where in the
+        // dependency chain things went bad. Don't list this for the first link
+        // in the chain since direct dependencies are OK, and listing that as
+        // "private" may make people feel like they need to fix it.
+        if (i == static_cast<int>(chain.size()) - 1 || chain[i - 1].is_public)
+          ret.append(" -->");
+        else
+          ret.append(" --[private]-->");
+      }
+      ret.append("\n");
+    }
+  }
+  return ret;
+}
+
+// Returns true if the two targets have the same label not counting the
+// toolchain.
+bool TargetLabelsMatchExceptToolchain(const Target* a, const Target* b) {
+  return a->label().dir() == b->label().dir() &&
+         a->label().name() == b->label().name();
+}
+
+// Returns true if the target |annotation_on| includes a friend annotation
+// that allows |is_marked_friend| as a friend.
+bool FriendMatches(const Target* annotation_on,
+                   const Target* is_marked_friend) {
+  return LabelPattern::VectorMatches(annotation_on->friends(),
+                                     is_marked_friend->label());
+}
+
+}  // namespace
+
+HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
+                             const std::vector<const Target*>& targets,
+                             bool check_generated,
+                             bool check_system)
+    : build_settings_(build_settings),
+      check_generated_(check_generated),
+      check_system_(check_system),
+      lock_(),
+      task_count_cv_() {
+  for (auto* target : targets)
+    AddTargetToFileMap(target, &file_map_);
+}
+
+HeaderChecker::~HeaderChecker() = default;
+
+bool HeaderChecker::Run(const std::vector<const Target*>& to_check,
+                        bool force_check,
+                        std::vector<Err>* errors) {
+  FileMap files_to_check;
+  for (auto* check : to_check) {
+    // This function will get called with all target types, but check only
+    // applies to binary targets.
+    if (check->IsBinary())
+      AddTargetToFileMap(check, &files_to_check);
+  }
+  RunCheckOverFiles(files_to_check, force_check);
+
+  if (errors_.empty())
+    return true;
+  *errors = errors_;
+  return false;
+}
+
+void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
+  WorkerPool pool;
+
+  for (const auto& file : files) {
+    // Only check C-like source files (RC files also have includes).
+    SourceFile::Type type = file.first.type();
+    if (type != SourceFile::SOURCE_CPP && type != SourceFile::SOURCE_H &&
+        type != SourceFile::SOURCE_C && type != SourceFile::SOURCE_M &&
+        type != SourceFile::SOURCE_MM && type != SourceFile::SOURCE_RC)
+      continue;
+
+    if (!check_generated_) {
+      // If any target marks it as generated, don't check it. We have to check
+      // file_map_, which includes all known files; files only includes those
+      // being checked.
+      bool is_generated = false;
+      for (const auto& vect_i : file_map_[file.first])
+        is_generated |= vect_i.is_generated;
+      if (is_generated)
+        continue;
+    }
+
+    for (const auto& vect_i : file.second) {
+      if (vect_i.target->check_includes()) {
+        task_count_.Increment();
+        pool.PostTask([this, target = vect_i.target, file = file.first]() {
+          DoWork(target, file);
+        });
+      }
+    }
+  }
+
+  // Wait for all tasks posted by this method to complete.
+  std::unique_lock<std::mutex> auto_lock(lock_);
+  while (!task_count_.IsZero())
+    task_count_cv_.wait(auto_lock);
+}
+
+void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
+  std::vector<Err> errors;
+  if (!CheckFile(target, file, &errors)) {
+    std::lock_guard<std::mutex> lock(lock_);
+    errors_.insert(errors_.end(), errors.begin(), errors.end());
+  }
+
+  if (!task_count_.Decrement()) {
+    // Signal |task_count_cv_| when |task_count_| becomes zero.
+    std::unique_lock<std::mutex> auto_lock(lock_);
+    task_count_cv_.notify_one();
+  }
+}
+
+// static
+void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
+  // Files in the sources have this public bit by default.
+  bool default_public = target->all_headers_public();
+
+  std::map<SourceFile, PublicGeneratedPair> files_to_public;
+
+  // First collect the normal files, they get the default visibility. If you
+  // depend on the compiled target, it should be enough to be able to include
+  // the header.
+  for (const auto& source : target->sources()) {
+    files_to_public[source].is_public = default_public;
+  }
+
+  // Add in the public files, forcing them to public. This may overwrite some
+  // entries, and it may add new ones.
+  if (default_public)  // List only used when default is not public.
+    DCHECK(target->public_headers().empty());
+  for (const auto& source : target->public_headers()) {
+    files_to_public[source].is_public = true;
+  }
+
+  // Add in outputs from actions. These are treated as public (since if other
+  // targets can't use them, then there wouldn't be any point in outputting).
+  std::vector<SourceFile> outputs;
+  target->action_values().GetOutputsAsSourceFiles(target, &outputs);
+  for (const auto& output : outputs) {
+    PublicGeneratedPair* pair = &files_to_public[output];
+    pair->is_public = true;
+    pair->is_generated = true;
+  }
+
+  // Add the merged list to the master list of all files.
+  for (const auto& cur : files_to_public) {
+    (*dest)[cur.first].push_back(
+        TargetInfo(target, cur.second.is_public, cur.second.is_generated));
+  }
+}
+
+bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
+  const std::string& build_dir = build_settings_->build_dir().value();
+  return file.value().compare(0, build_dir.size(), build_dir) == 0;
+}
+
+SourceFile HeaderChecker::SourceFileForInclude(
+    const IncludeStringWithLocation& include,
+    const std::vector<SourceDir>& include_dirs,
+    const InputFile& source_file,
+    Err* err) const {
+  using base::FilePath;
+
+  Value relative_file_value(nullptr, std::string(include.contents));
+
+  auto find_predicate = [relative_file_value, err, this](const SourceDir& dir) -> bool {
+        SourceFile include_file =
+            dir.ResolveRelativeFile(relative_file_value, err);
+        return file_map_.find(include_file) != file_map_.end();
+      };
+  if (!include.system_style_include) {
+    const SourceDir& file_dir = source_file.dir();
+    if (find_predicate(file_dir)) {
+      return file_dir.ResolveRelativeFile(relative_file_value, err);
+    }
+  }
+
+  auto it = std::find_if(
+      include_dirs.begin(), include_dirs.end(), find_predicate);
+
+  if (it != include_dirs.end())
+    return it->ResolveRelativeFile(relative_file_value, err);
+
+  return SourceFile();
+}
+
+bool HeaderChecker::CheckFile(const Target* from_target,
+                              const SourceFile& file,
+                              std::vector<Err>* errors) const {
+  ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
+
+  // Sometimes you have generated source files included as sources in another
+  // target. These won't exist at checking time. Since we require all generated
+  // files to be somewhere in the output tree, we can just check the name to
+  // see if they should be skipped.
+  if (!check_generated_ && IsFileInOuputDir(file))
+    return true;
+
+  base::FilePath path = build_settings_->GetFullPath(file);
+  std::string contents;
+  if (!base::ReadFileToString(path, &contents)) {
+    // A missing (not yet) generated file is an acceptable problem
+    // considering this code does not understand conditional includes.
+    if (IsFileInOuputDir(file))
+      return true;
+
+    errors->emplace_back(from_target->defined_from(), "Source file not found.",
+                         "The target:\n  " +
+                             from_target->label().GetUserVisibleName(false) +
+                             "\nhas a source file:\n  " + file.value() +
+                             "\nwhich was not found.");
+    return false;
+  }
+
+  InputFile input_file(file);
+  input_file.SetContents(contents);
+
+  std::vector<SourceDir> include_dirs;
+  for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
+    const std::vector<SourceDir>& target_include_dirs =
+        iter.cur().include_dirs();
+    include_dirs.insert(include_dirs.end(), target_include_dirs.begin(),
+                        target_include_dirs.end());
+  }
+
+  size_t error_count_before = errors->size();
+  CIncludeIterator iter(&input_file);
+
+  IncludeStringWithLocation include;
+
+  std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
+
+  while (iter.GetNextIncludeString(&include)) {
+    if (include.system_style_include && !check_system_)
+      continue;
+
+    Err err;
+    SourceFile included_file = SourceFileForInclude(include,
+                                                    include_dirs,
+                                                    input_file,
+                                                    &err);
+    if (!included_file.is_null()) {
+      CheckInclude(from_target, input_file, included_file, include.location,
+                   &no_dependency_cache, errors);
+    }
+  }
+
+  return errors->size() == error_count_before;
+}
+
+// If the file exists:
+//  - The header must be in the public section of a target, or it must
+//    be in the sources with no public list (everything is implicitly public).
+//  - The dependency path to the included target must follow only public_deps.
+//  - If there are multiple targets with the header in it, only one need be
+//    valid for the check to pass.
+void HeaderChecker::CheckInclude(
+    const Target* from_target,
+    const InputFile& source_file,
+    const SourceFile& include_file,
+    const LocationRange& range,
+    std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
+    std::vector<Err>* errors) const {
+  // Assume if the file isn't declared in our sources that we don't need to
+  // check it. It would be nice if we could give an error if this happens, but
+  // our include finder is too primitive and returns all includes, even if
+  // they're in a #if not executed in the current build. In that case, it's
+  // not unusual for the buildfiles to not specify that header at all.
+  FileMap::const_iterator found = file_map_.find(include_file);
+  if (found == file_map_.end())
+    return;
+
+  const TargetVector& targets = found->second;
+  Chain chain;  // Prevent reallocating in the loop.
+
+  // If the file is unknown in the current toolchain (rather than being private
+  // or in a target not visible to the current target), ignore it. This is a
+  // bit of a hack to account for the fact that the include finder doesn't
+  // understand the preprocessor.
+  //
+  // When not cross-compiling, if a platform specific header is conditionally
+  // included in the build, and preprocessor conditions around #includes of
+  // that match the build conditions, everything will be OK because the file
+  // won't be known to GN even though the #include finder identified the file.
+  //
+  // Cross-compiling breaks this. When compiling Android on Linux, for example,
+  // we might see both Linux and Android definitions of a target and know
+  // about the union of all headers in the build. Since the #include finder
+  // ignores preprocessor, we will find the Linux headers in the Android
+  // build and note that a dependency from the Android target to the Linux
+  // one is missing (these might even be the same target in different
+  // toolchains!).
+  bool present_in_current_toolchain = false;
+  for (const auto& target : targets) {
+    if (from_target->label().ToolchainsEqual(target.target->label())) {
+      present_in_current_toolchain = true;
+      break;
+    }
+  }
+  if (!present_in_current_toolchain)
+    return;
+
+  // For all targets containing this file, we require that at least one be
+  // a direct or public dependency of the current target, and either (1) the
+  // header is public within the target, or (2) there is a friend definition
+  // whitelisting the includor.
+  //
+  // If there is more than one target containing this header, we may encounter
+  // some error cases before finding a good one. This error stores the previous
+  // one encountered, which we may or may not throw away.
+  Err last_error;
+
+  bool found_dependency = false;
+  for (const auto& target : targets) {
+    // We always allow source files in a target to include headers also in that
+    // target.
+    const Target* to_target = target.target;
+    if (to_target == from_target)
+      return;
+
+    bool is_permitted_chain = false;
+
+    bool cached_no_dependency =
+        no_dependency_cache->find(std::make_pair(to_target, from_target)) !=
+        no_dependency_cache->end();
+
+    bool add_to_cache = !cached_no_dependency;
+
+    if (!cached_no_dependency &&
+        IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
+      add_to_cache = false;
+
+      DCHECK(chain.size() >= 2);
+      DCHECK(chain[0].target == to_target);
+      DCHECK(chain[chain.size() - 1].target == from_target);
+
+      found_dependency = true;
+
+      bool effectively_public =
+          target.is_public || FriendMatches(to_target, from_target);
+
+      if (effectively_public && is_permitted_chain) {
+        // This one is OK, we're done.
+        last_error = Err();
+        break;
+      }
+
+      // Diagnose the error.
+      if (!effectively_public) {
+        // Danger: must call CreatePersistentRange to put in Err.
+        last_error = Err(CreatePersistentRange(source_file, range),
+                         "Including a private header.",
+                         "This file is private to the target " +
+                             target.target->label().GetUserVisibleName(false));
+      } else if (!is_permitted_chain) {
+        last_error = Err(CreatePersistentRange(source_file, range),
+                         "Can't include this header from here.",
+                         GetDependencyChainPublicError(chain));
+      } else {
+        NOTREACHED();
+      }
+    } else if (to_target->allow_circular_includes_from().find(
+                   from_target->label()) !=
+               to_target->allow_circular_includes_from().end()) {
+      // Not a dependency, but this include is whitelisted from the destination.
+      found_dependency = true;
+      last_error = Err();
+      break;
+    }
+
+    if (add_to_cache) {
+      no_dependency_cache->emplace(to_target, from_target);
+    }
+  }
+
+  if (!found_dependency || last_error.has_error()) {
+    if (!found_dependency) {
+      DCHECK(!last_error.has_error());
+      Err err = MakeUnreachableError(source_file, range, from_target, targets);
+      errors->push_back(std::move(err));
+    } else {
+      // Found at least one dependency chain above, but it had an error.
+      errors->push_back(std::move(last_error));
+    }
+    return;
+  }
+
+  // One thing we didn't check for is targets that expose their dependents
+  // headers in their own public headers.
+  //
+  // Say we have A -> B -> C. If C has public_configs, everybody getting headers
+  // from C should get the configs also or things could be out-of-sync. Above,
+  // we check for A including C's headers directly, but A could also include a
+  // header from B that in turn includes a header from C.
+  //
+  // There are two ways to solve this:
+  //  - If a public header in B includes C, force B to publicly depend on C.
+  //    This is possible to check, but might be super annoying because most
+  //    targets (especially large leaf-node targets) don't declare
+  //    public/private headers and you'll get lots of false positives.
+  //
+  //  - Save the includes found in each file and actually compute the graph of
+  //    includes to detect when A implicitly includes C's header. This will not
+  //    have the annoying false positive problem, but is complex to write.
+}
+
+bool HeaderChecker::IsDependencyOf(const Target* search_for,
+                                   const Target* search_from,
+                                   Chain* chain,
+                                   bool* is_permitted) const {
+  if (search_for == search_from) {
+    // A target is always visible from itself.
+    *is_permitted = true;
+    return false;
+  }
+
+  // Find the shortest public dependency chain.
+  if (IsDependencyOf(search_for, search_from, true, chain)) {
+    *is_permitted = true;
+    return true;
+  }
+
+  // If not, try to find any dependency chain at all.
+  if (IsDependencyOf(search_for, search_from, false, chain)) {
+    *is_permitted = false;
+    return true;
+  }
+
+  *is_permitted = false;
+  return false;
+}
+
+bool HeaderChecker::IsDependencyOf(const Target* search_for,
+                                   const Target* search_from,
+                                   bool require_permitted,
+                                   Chain* chain) const {
+  // This method conducts a breadth-first search through the dependency graph
+  // to find a shortest chain from search_from to search_for.
+  //
+  // work_queue maintains a queue of targets which need to be considered as
+  // part of this chain, in the order they were first traversed.
+  //
+  // Each time a new transitive dependency of search_from is discovered for
+  // the first time, it is added to work_queue and a "breadcrumb" is added,
+  // indicating which target it was reached from when first discovered.
+  //
+  // Once this search finds search_for, the breadcrumbs are used to reconstruct
+  // a shortest dependency chain (in reverse order) from search_from to
+  // search_for.
+
+  std::map<const Target*, ChainLink> breadcrumbs;
+  base::queue<ChainLink> work_queue;
+  work_queue.push(ChainLink(search_from, true));
+
+  bool first_time = true;
+  while (!work_queue.empty()) {
+    ChainLink cur_link = work_queue.front();
+    const Target* target = cur_link.target;
+    work_queue.pop();
+
+    if (target == search_for) {
+      // Found it! Reconstruct the chain.
+      chain->clear();
+      while (target != search_from) {
+        chain->push_back(cur_link);
+        cur_link = breadcrumbs[target];
+        target = cur_link.target;
+      }
+      chain->push_back(ChainLink(search_from, true));
+      return true;
+    }
+
+    // Always consider public dependencies as possibilities.
+    for (const auto& dep : target->public_deps()) {
+      if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
+        work_queue.push(ChainLink(dep.ptr, true));
+    }
+
+    if (first_time || !require_permitted) {
+      // Consider all dependencies since all target paths are allowed, so add
+      // in private ones. Also do this the first time through the loop, since
+      // a target can include headers from its direct deps regardless of
+      // public/private-ness.
+      first_time = false;
+      for (const auto& dep : target->private_deps()) {
+        if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
+          work_queue.push(ChainLink(dep.ptr, false));
+      }
+    }
+  }
+
+  return false;
+}
+
+Err HeaderChecker::MakeUnreachableError(const InputFile& source_file,
+                                        const LocationRange& range,
+                                        const Target* from_target,
+                                        const TargetVector& targets) {
+  // Normally the toolchains will all match, but when cross-compiling, we can
+  // get targets with more than one toolchain in the list of possibilities.
+  std::vector<const Target*> targets_with_matching_toolchains;
+  std::vector<const Target*> targets_with_other_toolchains;
+  for (const TargetInfo& candidate : targets) {
+    if (candidate.target->toolchain() == from_target->toolchain())
+      targets_with_matching_toolchains.push_back(candidate.target);
+    else
+      targets_with_other_toolchains.push_back(candidate.target);
+  }
+
+  // It's common when cross-compiling to have a target with the same file in
+  // more than one toolchain. We could output all of them, but this is
+  // generally confusing to people (most end-users won't understand toolchains
+  // well).
+  //
+  // So delete any candidates in other toolchains that also appear in the same
+  // toolchain as the from_target.
+  for (int other_index = 0;
+       other_index < static_cast<int>(targets_with_other_toolchains.size());
+       other_index++) {
+    for (const Target* cur_matching : targets_with_matching_toolchains) {
+      if (TargetLabelsMatchExceptToolchain(
+              cur_matching, targets_with_other_toolchains[other_index])) {
+        // Found a duplicate, erase it.
+        targets_with_other_toolchains.erase(
+            targets_with_other_toolchains.begin() + other_index);
+        other_index--;
+        break;
+      }
+    }
+  }
+
+  // Only display toolchains on labels if they don't all match.
+  bool include_toolchain = !targets_with_other_toolchains.empty();
+
+  std::string msg = "It is not in any dependency of\n  " +
+                    from_target->label().GetUserVisibleName(include_toolchain);
+  msg += "\nThe include file is in the target(s):\n";
+  for (auto* target : targets_with_matching_toolchains)
+    msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
+  for (auto* target : targets_with_other_toolchains)
+    msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
+  if (targets_with_other_toolchains.size() +
+          targets_with_matching_toolchains.size() >
+      1)
+    msg += "at least one of ";
+  msg += "which should somehow be reachable.";
+
+  // Danger: must call CreatePersistentRange to put in Err.
+  return Err(CreatePersistentRange(source_file, range), "Include not allowed.",
+             msg);
+}
diff --git a/src/gn/header_checker.h b/src/gn/header_checker.h
new file mode 100644 (file)
index 0000000..9e10652
--- /dev/null
@@ -0,0 +1,208 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_HEADER_CHECKER_H_
+#define TOOLS_GN_HEADER_CHECKER_H_
+
+#include <condition_variable>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <set>
+#include <string_view>
+#include <vector>
+
+#include "base/atomic_ref_count.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "gn/c_include_iterator.h"
+#include "gn/err.h"
+#include "gn/source_dir.h"
+
+class BuildSettings;
+class InputFile;
+class SourceFile;
+class Target;
+
+namespace base {
+class FilePath;
+}
+
+class HeaderChecker : public base::RefCountedThreadSafe<HeaderChecker> {
+ public:
+  // Represents a dependency chain.
+  struct ChainLink {
+    ChainLink() : target(nullptr), is_public(false) {}
+    ChainLink(const Target* t, bool p) : target(t), is_public(p) {}
+
+    const Target* target;
+
+    // True when the dependency on this target is public.
+    bool is_public;
+
+    // Used for testing.
+    bool operator==(const ChainLink& other) const {
+      return target == other.target && is_public == other.is_public;
+    }
+  };
+  using Chain = std::vector<ChainLink>;
+
+  // check_generated, if true, will also check generated
+  // files. Something that can only be done after running a build that
+  // has generated them.
+  HeaderChecker(const BuildSettings* build_settings,
+                const std::vector<const Target*>& targets,
+                bool check_generated,
+                bool check_system);
+
+  // Runs the check. The targets in to_check will be checked.
+  //
+  // This assumes that the current thread already has a message loop. On
+  // error, fills the given vector with the errors and returns false. Returns
+  // true on success.
+  //
+  // force_check, if true, will override targets opting out of header checking
+  // with "check_includes = false" and will check them anyway.
+  bool Run(const std::vector<const Target*>& to_check,
+           bool force_check,
+           std::vector<Err>* errors);
+
+ private:
+  friend class base::RefCountedThreadSafe<HeaderChecker>;
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, IsDependencyOf);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckInclude);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, PublicFirst);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, CheckIncludeAllowCircular);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, SourceFileForInclude);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest,
+                           SourceFileForInclude_FileNotFound);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, Friend);
+
+  ~HeaderChecker();
+
+  struct TargetInfo {
+    TargetInfo() : target(nullptr), is_public(false), is_generated(false) {}
+    TargetInfo(const Target* t, bool is_pub, bool is_gen)
+        : target(t), is_public(is_pub), is_generated(is_gen) {}
+
+    const Target* target;
+
+    // True if the file is public in the given target.
+    bool is_public;
+
+    // True if this file is generated and won't actually exist on disk.
+    bool is_generated;
+  };
+
+  using TargetVector = std::vector<TargetInfo>;
+  using FileMap = std::map<SourceFile, TargetVector>;
+  using PathExistsCallback = std::function<bool(const base::FilePath& path)>;
+
+  // Backend for Run() that takes the list of files to check. The errors_ list
+  // will be populate on failure.
+  void RunCheckOverFiles(const FileMap& flies, bool force_check);
+
+  void DoWork(const Target* target, const SourceFile& file);
+
+  // Adds the sources and public files from the given target to the given map.
+  static void AddTargetToFileMap(const Target* target, FileMap* dest);
+
+  // Returns true if the given file is in the output directory.
+  bool IsFileInOuputDir(const SourceFile& file) const;
+
+  // Resolves the contents of an include to a SourceFile.
+  SourceFile SourceFileForInclude(const IncludeStringWithLocation& include,
+                                  const std::vector<SourceDir>& include_dirs,
+                                  const InputFile& source_file,
+                                  Err* err) const;
+
+  // from_target is the target the file was defined from. It will be used in
+  // error messages.
+  bool CheckFile(const Target* from_target,
+                 const SourceFile& file,
+                 std::vector<Err>* err) const;
+
+  // Checks that the given file in the given target can include the
+  // given include file. If disallowed, adds the error or errors to
+  // the errors array.  The range indicates the location of the
+  // include in the file for error reporting.
+  // |no_depeency_cache| is used to cache or check whether there is no
+  // dependency from |from_target| to target having |include_file|.
+  void CheckInclude(
+      const Target* from_target,
+      const InputFile& source_file,
+      const SourceFile& include_file,
+      const LocationRange& range,
+      std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
+      std::vector<Err>* errors) const;
+
+  // Returns true if the given search_for target is a dependency of
+  // search_from.
+  //
+  // If found, the vector given in "chain" will be filled with the reverse
+  // dependency chain from the dest target (chain[0] = search_for) to the src
+  // target (chain[chain.size() - 1] = search_from).
+  //
+  // Chains with permitted dependencies will be considered first. If a
+  // permitted match is found, *is_permitted will be set to true. A chain with
+  // indirect, non-public dependencies will only be considered if there are no
+  // public or direct chains. In this case, *is_permitted will be false.
+  //
+  // A permitted dependency is a sequence of public dependencies. The first
+  // one may be private, since a direct dependency always allows headers to be
+  // included.
+  bool IsDependencyOf(const Target* search_for,
+                      const Target* search_from,
+                      Chain* chain,
+                      bool* is_permitted) const;
+
+  // For internal use by the previous override of IsDependencyOf.  If
+  // require_public is true, only public dependency chains are searched.
+  bool IsDependencyOf(const Target* search_for,
+                      const Target* search_from,
+                      bool require_permitted,
+                      Chain* chain) const;
+
+  // Makes a very descriptive error message for when an include is disallowed
+  // from a given from_target, with a missing dependency to one of the given
+  // targets.
+  static Err MakeUnreachableError(const InputFile& source_file,
+                                  const LocationRange& range,
+                                  const Target* from_target,
+                                  const TargetVector& targets);
+
+  // Non-locked variables ------------------------------------------------------
+  //
+  // These are initialized during construction (which happens on one thread)
+  // and are not modified after, so any thread can read these without locking.
+
+  const BuildSettings* build_settings_;
+
+  bool check_generated_;
+
+  bool check_system_;
+
+  // Maps source files to targets it appears in (usually just one target).
+  FileMap file_map_;
+
+  // Number of tasks posted by RunCheckOverFiles() that haven't completed their
+  // execution.
+  base::AtomicRefCount task_count_;
+
+  // Locked variables ----------------------------------------------------------
+  //
+  // These are mutable during runtime and require locking.
+
+  std::mutex lock_;
+
+  std::vector<Err> errors_;
+
+  // Signaled when |task_count_| becomes zero.
+  std::condition_variable task_count_cv_;
+
+  DISALLOW_COPY_AND_ASSIGN(HeaderChecker);
+};
+
+#endif  // TOOLS_GN_HEADER_CHECKER_H_
diff --git a/src/gn/header_checker_unittest.cc b/src/gn/header_checker_unittest.cc
new file mode 100644 (file)
index 0000000..a290d45
--- /dev/null
@@ -0,0 +1,427 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ostream>
+#include <vector>
+
+#include "gn/config.h"
+#include "gn/header_checker.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+class HeaderCheckerTest : public TestWithScheduler {
+ public:
+  HeaderCheckerTest()
+      : a_(setup_.settings(), Label(SourceDir("//a/"), "a")),
+        b_(setup_.settings(), Label(SourceDir("//b/"), "b")),
+        c_(setup_.settings(), Label(SourceDir("//c/"), "c")),
+        d_(setup_.settings(), Label(SourceDir("//d/"), "d")) {
+    a_.set_output_type(Target::SOURCE_SET);
+    b_.set_output_type(Target::SOURCE_SET);
+    c_.set_output_type(Target::SOURCE_SET);
+    d_.set_output_type(Target::SOURCE_SET);
+
+    Err err;
+    a_.SetToolchain(setup_.toolchain(), &err);
+    b_.SetToolchain(setup_.toolchain(), &err);
+    c_.SetToolchain(setup_.toolchain(), &err);
+    d_.SetToolchain(setup_.toolchain(), &err);
+
+    a_.public_deps().push_back(LabelTargetPair(&b_));
+    b_.public_deps().push_back(LabelTargetPair(&c_));
+
+    // Start with all public visibility.
+    a_.visibility().SetPublic();
+    b_.visibility().SetPublic();
+    c_.visibility().SetPublic();
+    d_.visibility().SetPublic();
+
+    d_.OnResolved(&err);
+    c_.OnResolved(&err);
+    b_.OnResolved(&err);
+    a_.OnResolved(&err);
+
+    targets_.push_back(&a_);
+    targets_.push_back(&b_);
+    targets_.push_back(&c_);
+    targets_.push_back(&d_);
+  }
+
+ protected:
+  scoped_refptr<HeaderChecker> CreateChecker() {
+    bool check_generated = false;
+    bool check_system = true;
+    return base::MakeRefCounted<HeaderChecker>(
+        setup_.build_settings(), targets_, check_generated, check_system);
+  }
+
+  TestWithScope setup_;
+
+  // Some headers that are automatically set up with a public dependency chain.
+  // a -> b -> c. D is unconnected.
+  Target a_;
+  Target b_;
+  Target c_;
+  Target d_;
+
+  std::vector<const Target*> targets_;
+};
+
+}  // namespace
+
+void PrintTo(const SourceFile& source_file, ::std::ostream* os) {
+  *os << source_file.value();
+}
+
+TEST_F(HeaderCheckerTest, IsDependencyOf) {
+  auto checker = CreateChecker();
+
+  // Add a target P ("private") that privately depends on C, and hook up the
+  // chain so that A -> P -> C. A will depend on C via two different paths.
+  Err err;
+  Target p(setup_.settings(), Label(SourceDir("//p/"), "p"));
+  p.set_output_type(Target::SOURCE_SET);
+  p.SetToolchain(setup_.toolchain(), &err);
+  EXPECT_FALSE(err.has_error());
+  p.private_deps().push_back(LabelTargetPair(&c_));
+  p.visibility().SetPublic();
+  p.OnResolved(&err);
+
+  a_.public_deps().push_back(LabelTargetPair(&p));
+
+  // A does not depend on itself.
+  bool is_permitted = false;
+  HeaderChecker::Chain chain;
+  EXPECT_FALSE(checker->IsDependencyOf(&a_, &a_, &chain, &is_permitted));
+
+  // A depends publicly on B.
+  chain.clear();
+  is_permitted = false;
+  EXPECT_TRUE(checker->IsDependencyOf(&b_, &a_, &chain, &is_permitted));
+  ASSERT_EQ(2u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[1]);
+  EXPECT_TRUE(is_permitted);
+
+  // A indirectly depends on C. The "public" dependency path through B should
+  // be identified.
+  chain.clear();
+  is_permitted = false;
+  EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted));
+  ASSERT_EQ(3u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[1]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]);
+  EXPECT_TRUE(is_permitted);
+
+  // C does not depend on A.
+  chain.clear();
+  is_permitted = false;
+  EXPECT_FALSE(checker->IsDependencyOf(&a_, &c_, &chain, &is_permitted));
+  EXPECT_TRUE(chain.empty());
+  EXPECT_FALSE(is_permitted);
+
+  // Remove the B -> C public dependency, leaving P's private dep on C the only
+  // path from A to C. This should now be found.
+  chain.clear();
+  EXPECT_EQ(&c_, b_.public_deps()[0].ptr);  // Validate it's the right one.
+  b_.public_deps().erase(b_.public_deps().begin());
+  EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted));
+  EXPECT_EQ(3u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]);
+  EXPECT_FALSE(is_permitted);
+
+  // P privately depends on C. That dependency should be OK since it's only
+  // one hop.
+  chain.clear();
+  is_permitted = false;
+  EXPECT_TRUE(checker->IsDependencyOf(&c_, &p, &chain, &is_permitted));
+  ASSERT_EQ(2u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]);
+  EXPECT_TRUE(is_permitted);
+}
+
+TEST_F(HeaderCheckerTest, CheckInclude) {
+  InputFile input_file(SourceFile("//some_file.cc"));
+  input_file.SetContents(std::string());
+  LocationRange range;  // Dummy value.
+
+  // Add a disconnected target d with a header to check that you have to have
+  // to depend on a target listing a header.
+  SourceFile d_header("//d_header.h");
+  d_.sources().push_back(SourceFile(d_header));
+
+  // Add a header on B and say everything in B is public.
+  SourceFile b_public("//b_public.h");
+  b_.sources().push_back(b_public);
+  c_.set_all_headers_public(true);
+
+  // Add a public and private header on C.
+  SourceFile c_public("//c_public.h");
+  SourceFile c_private("//c_private.h");
+  c_.sources().push_back(c_private);
+  c_.public_headers().push_back(c_public);
+  c_.set_all_headers_public(false);
+
+  // Create another toolchain.
+  Settings other_settings(setup_.build_settings(), "other/");
+  Toolchain other_toolchain(&other_settings,
+                            Label(SourceDir("//toolchain/"), "other"));
+  TestWithScope::SetupToolchain(&other_toolchain);
+  other_settings.set_toolchain_label(other_toolchain.label());
+  other_settings.set_default_toolchain_label(setup_.toolchain()->label());
+
+  // Add a target in the other toolchain with a header in it that is not
+  // connected to any targets in the main toolchain.
+  Target otc(&other_settings,
+             Label(SourceDir("//p/"), "otc", other_toolchain.label().dir(),
+                   other_toolchain.label().name()));
+  otc.set_output_type(Target::SOURCE_SET);
+  Err err;
+  EXPECT_TRUE(otc.SetToolchain(&other_toolchain, &err));
+  otc.visibility().SetPublic();
+  targets_.push_back(&otc);
+
+  SourceFile otc_header("//otc_header.h");
+  otc.sources().push_back(otc_header);
+  EXPECT_TRUE(otc.OnResolved(&err));
+
+  auto checker = CreateChecker();
+
+  std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
+  // A file in target A can't include a header from D because A has no
+  // dependency on D.
+  std::vector<Err> errors;
+  checker->CheckInclude(&a_, input_file, d_header, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_GT(errors.size(), 0);
+
+  // A can include the public header in B.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, b_public, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_EQ(errors.size(), 0);
+
+  // Check A depending on the public and private headers in C.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, c_public, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_EQ(errors.size(), 0);
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_GT(errors.size(), 0);
+
+  // A can depend on a random file unknown to the build.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, SourceFile("//random.h"), range,
+                        &no_dependency_cache, &errors);
+  EXPECT_EQ(errors.size(), 0);
+
+  // A can depend on a file present only in another toolchain even with no
+  // dependency path.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, otc_header, range,
+                        &no_dependency_cache, &errors);
+  EXPECT_EQ(errors.size(), 0);
+}
+
+// A public chain of dependencies should always be identified first, even if
+// it is longer than a private one.
+TEST_F(HeaderCheckerTest, PublicFirst) {
+  // Now make a A -> Z -> D private dependency chain (one shorter than the
+  // public one to get to D).
+  Target z(setup_.settings(), Label(SourceDir("//a/"), "a"));
+  z.set_output_type(Target::SOURCE_SET);
+  Err err;
+  EXPECT_TRUE(z.SetToolchain(setup_.toolchain(), &err));
+  z.private_deps().push_back(LabelTargetPair(&d_));
+  EXPECT_TRUE(z.OnResolved(&err));
+  targets_.push_back(&z);
+
+  a_.private_deps().push_back(LabelTargetPair(&z));
+
+  // Check that D can be found from A, but since it's private, it will be
+  // marked as not permitted.
+  bool is_permitted = false;
+  HeaderChecker::Chain chain;
+  auto checker = CreateChecker();
+  EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted));
+
+  EXPECT_FALSE(is_permitted);
+  ASSERT_EQ(3u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&d_, false), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&z, false), chain[1]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]);
+
+  // Hook up D to the existing public A -> B -> C chain to make a long one, and
+  // search for D again.
+  c_.public_deps().push_back(LabelTargetPair(&d_));
+  checker = CreateChecker();
+  chain.clear();
+  EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted));
+
+  // This should have found the long public one.
+  EXPECT_TRUE(is_permitted);
+  ASSERT_EQ(4u, chain.size());
+  EXPECT_EQ(HeaderChecker::ChainLink(&d_, true), chain[0]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[1]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[2]);
+  EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[3]);
+}
+
+// Checks that the allow_circular_includes_from list works.
+TEST_F(HeaderCheckerTest, CheckIncludeAllowCircular) {
+  InputFile input_file(SourceFile("//some_file.cc"));
+  input_file.SetContents(std::string());
+  LocationRange range;  // Dummy value.
+
+  // Add an include file to A.
+  SourceFile a_public("//a_public.h");
+  a_.sources().push_back(a_public);
+
+  auto checker = CreateChecker();
+
+  // A depends on B. So B normally can't include headers from A.
+  std::vector<Err> errors;
+  std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
+  checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_GT(errors.size(), 0);
+
+  // Add an allow_circular_includes_from on A that lists B.
+  a_.allow_circular_includes_from().insert(b_.label());
+
+  // Now the include from B to A should be allowed.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_EQ(errors.size(), 0);
+}
+
+TEST_F(HeaderCheckerTest, SourceFileForInclude) {
+  using base::FilePath;
+  const std::vector<SourceDir> kIncludeDirs = {
+      SourceDir("/c/custom_include/"), SourceDir("//"), SourceDir("//subdir")};
+  a_.sources().push_back(SourceFile("//lib/header1.h"));
+  b_.sources().push_back(SourceFile("/c/custom_include/header2.h"));
+  d_.sources().push_back(SourceFile("/d/subdir/header3.h"));
+
+  InputFile dummy_input_file(SourceFile("/d/subdir/some_file.cc"));
+  dummy_input_file.SetContents(std::string());
+
+  auto checker = CreateChecker();
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "lib/header1.h";
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_FALSE(err.has_error());
+    EXPECT_EQ(SourceFile("//lib/header1.h"), source_file);
+  }
+
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header2.h";
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_FALSE(err.has_error());
+    EXPECT_EQ(SourceFile("/c/custom_include/header2.h"), source_file);
+  }
+
+  // A non system style include should find a header file in the same directory
+  // as the source file, regardless of include dirs.
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header3.h";
+    include.system_style_include = false;
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_FALSE(err.has_error());
+    EXPECT_EQ(SourceFile("/d/subdir/header3.h"), source_file);
+  }
+
+  // A system style include should *not* find a header file in the same
+  // directory as the source file if that directory is not in the include dirs.
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header3.h";
+    include.system_style_include = true;
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_TRUE(source_file.is_null());
+    EXPECT_FALSE(err.has_error());
+  }
+}
+
+TEST_F(HeaderCheckerTest, SourceFileForInclude_FileNotFound) {
+  using base::FilePath;
+  const char kFileContents[] = "Some dummy contents";
+  const std::vector<SourceDir> kIncludeDirs = {SourceDir("//")};
+  auto checker = CreateChecker();
+
+  Err err;
+  InputFile input_file(SourceFile("//input.cc"));
+  input_file.SetContents(std::string(kFileContents));
+
+  IncludeStringWithLocation include;
+  include.contents = "header.h";
+  SourceFile source_file =
+      checker->SourceFileForInclude(include, kIncludeDirs, input_file, &err);
+  EXPECT_TRUE(source_file.is_null());
+  EXPECT_FALSE(err.has_error());
+}
+
+TEST_F(HeaderCheckerTest, Friend) {
+  // Note: we have a public dependency chain A -> B -> C set up already.
+  InputFile input_file(SourceFile("//some_file.cc"));
+  input_file.SetContents(std::string());
+  LocationRange range;  // Dummy value.
+
+  // Add a private header on C.
+  SourceFile c_private("//c_private.h");
+  c_.sources().push_back(c_private);
+  c_.set_all_headers_public(false);
+
+  // List A as a friend of C.
+  Err err;
+  c_.friends().push_back(LabelPattern::GetPattern(
+      SourceDir("//"), std::string_view(), Value(nullptr, "//a:*"), &err));
+  ASSERT_FALSE(err.has_error());
+
+  // Must be after setting everything up for it to find the files.
+  auto checker = CreateChecker();
+
+  // B should not be allowed to include C's private header.
+  std::vector<Err> errors;
+  std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
+  checker->CheckInclude(&b_, input_file, c_private, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_GT(errors.size(), 0);
+
+  // A should be able to because of the friend declaration.
+  errors.clear();
+  no_dependency_cache.clear();
+  checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache,
+                        &errors);
+  EXPECT_EQ(errors.size(), 0);
+}
diff --git a/src/gn/import_manager.cc b/src/gn/import_manager.cc
new file mode 100644 (file)
index 0000000..a4928bf
--- /dev/null
@@ -0,0 +1,162 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/import_manager.h"
+
+#include <memory>
+
+#include "gn/err.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/scope_per_file_provider.h"
+#include "gn/trace.h"
+#include "util/ticks.h"
+
+namespace {
+
+// Returns a newly-allocated scope on success, null on failure.
+std::unique_ptr<Scope> UncachedImport(const Settings* settings,
+                                      const SourceFile& file,
+                                      const ParseNode* node_for_err,
+                                      Err* err) {
+  ScopedTrace load_trace(TraceItem::TRACE_IMPORT_LOAD, file.value());
+  load_trace.SetToolchain(settings->toolchain_label());
+
+  const ParseNode* node = g_scheduler->input_file_manager()->SyncLoadFile(
+      node_for_err->GetRange(), settings->build_settings(), file, err);
+  if (!node)
+    return nullptr;
+
+  std::unique_ptr<Scope> scope =
+      std::make_unique<Scope>(settings->base_config());
+  scope->set_source_dir(file.GetDir());
+
+  // Don't allow ScopePerFileProvider to provide target-related variables.
+  // These will be relative to the imported file, which is probably not what
+  // people mean when they use these.
+  ScopePerFileProvider per_file_provider(scope.get(), false);
+
+  scope->SetProcessingImport();
+  node->Execute(scope.get(), err);
+  if (err->has_error()) {
+    // If there was an error, append the caller location so the error message
+    // displays a why the file was imported (esp. useful for failed asserts).
+    err->AppendSubErr(Err(node_for_err, "whence it was imported."));
+    return nullptr;
+  }
+  scope->ClearProcessingImport();
+
+  return scope;
+}
+
+}  // namespace
+
+struct ImportManager::ImportInfo {
+  ImportInfo() = default;
+  ~ImportInfo() = default;
+
+  // This lock protects the unique_ptr. Once the scope is computed,
+  // it is const and can be accessed read-only outside of the lock.
+  std::mutex load_lock;
+
+  std::unique_ptr<const Scope> scope;
+
+  // The result of loading the import. If the load failed, the scope will be
+  // null but this will be set to error. In this case the thread should not
+  // attempt to load the file, even if the scope is null.
+  Err load_result;
+};
+
+ImportManager::ImportManager() = default;
+
+ImportManager::~ImportManager() = default;
+
+bool ImportManager::DoImport(const SourceFile& file,
+                             const ParseNode* node_for_err,
+                             Scope* scope,
+                             Err* err) {
+  // Key for the current import on the current thread in imports_in_progress_.
+  std::stringstream ss;
+  ss << std::this_thread::get_id() << file.value();
+  std::string key = ss.str();
+
+  // See if we have a cached import, but be careful to actually do the scope
+  // copying outside of the lock.
+  ImportInfo* import_info = nullptr;
+  {
+    std::lock_guard<std::mutex> lock(imports_lock_);
+    std::unique_ptr<ImportInfo>& info_ptr = imports_[file];
+    if (!info_ptr)
+      info_ptr = std::make_unique<ImportInfo>();
+
+    // Promote the ImportInfo to outside of the imports lock.
+    import_info = info_ptr.get();
+
+    if (imports_in_progress_.find(key) != imports_in_progress_.end()) {
+      *err = Err(Location(), file.value() + " is part of an import loop.");
+      return false;
+    }
+    imports_in_progress_.insert(key);
+  }
+
+  // Now use the per-import-file lock to block this thread if another thread
+  // is already processing the import.
+  const Scope* import_scope = nullptr;
+  {
+    Ticks import_block_begin = TicksNow();
+    std::lock_guard<std::mutex> lock(import_info->load_lock);
+
+    if (!import_info->scope) {
+      // Only load if the import hasn't already failed.
+      if (!import_info->load_result.has_error()) {
+        import_info->scope = UncachedImport(
+            scope->settings(), file, node_for_err, &import_info->load_result);
+      }
+      if (import_info->load_result.has_error()) {
+        *err = import_info->load_result;
+        return false;
+      }
+    } else {
+      // Add trace if this thread was blocked for a long period of time and did
+      // not load the import itself.
+      Ticks import_block_end = TicksNow();
+      constexpr auto kImportBlockTraceThresholdMS = 20;
+      if (TracingEnabled() &&
+          TicksDelta(import_block_end, import_block_begin).InMilliseconds() >
+              kImportBlockTraceThresholdMS) {
+        auto* import_block_trace =
+            new TraceItem(TraceItem::TRACE_IMPORT_BLOCK, file.value(),
+                          std::this_thread::get_id());
+        import_block_trace->set_begin(import_block_begin);
+        import_block_trace->set_end(import_block_end);
+        import_block_trace->set_toolchain(
+            scope->settings()->toolchain_label().GetUserVisibleName(false));
+        AddTrace(import_block_trace);
+      }
+    }
+
+    // Promote the now-read-only scope to outside the load lock.
+    import_scope = import_info->scope.get();
+  }
+
+  Scope::MergeOptions options;
+  options.skip_private_vars = true;
+  options.mark_dest_used = true;  // Don't require all imported values be used.
+
+  {
+    std::lock_guard<std::mutex> lock(imports_lock_);
+    imports_in_progress_.erase(key);
+  }
+
+  return import_scope->NonRecursiveMergeTo(scope, options, node_for_err,
+                                           "import", err);
+}
+
+std::vector<SourceFile> ImportManager::GetImportedFiles() const {
+  std::vector<SourceFile> imported_files;
+  imported_files.resize(imports_.size());
+  std::transform(imports_.begin(), imports_.end(), imported_files.begin(),
+                 [](const ImportMap::value_type& val) { return val.first; });
+  return imported_files;
+}
diff --git a/src/gn/import_manager.h b/src/gn/import_manager.h
new file mode 100644 (file)
index 0000000..e033183
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_IMPORT_MANAGER_H_
+#define TOOLS_GN_IMPORT_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "base/macros.h"
+
+class Err;
+class ParseNode;
+class Scope;
+class SourceFile;
+
+// Provides a cache of the results of importing scopes so the results can
+// be re-used rather than running the imported files multiple times.
+class ImportManager {
+ public:
+  ImportManager();
+  ~ImportManager();
+
+  // Does an import of the given file into the given scope. On error, sets the
+  // error and returns false.
+  bool DoImport(const SourceFile& file,
+                const ParseNode* node_for_err,
+                Scope* scope,
+                Err* err);
+
+  std::vector<SourceFile> GetImportedFiles() const;
+
+ private:
+  struct ImportInfo;
+
+  // Protects access to imports_ and imports_in_progress_. Do not hold when
+  // actually executing imports.
+  std::mutex imports_lock_;
+
+  // Owning pointers to the scopes.
+  using ImportMap = std::map<SourceFile, std::unique_ptr<ImportInfo>>;
+  ImportMap imports_;
+
+  std::unordered_set<std::string> imports_in_progress_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImportManager);
+};
+
+#endif  // TOOLS_GN_IMPORT_MANAGER_H_
diff --git a/src/gn/inherited_libraries.cc b/src/gn/inherited_libraries.cc
new file mode 100644 (file)
index 0000000..37e0b97
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/inherited_libraries.h"
+
+#include "gn/target.h"
+
+InheritedLibraries::InheritedLibraries() = default;
+
+InheritedLibraries::~InheritedLibraries() = default;
+
+std::vector<const Target*> InheritedLibraries::GetOrdered() const {
+  std::vector<const Target*> result;
+  result.resize(map_.size());
+
+  // The indices in the map should be from 0 to the number of items in the
+  // map, so insert directly into the result (with some sanity checks).
+  for (const auto& pair : map_) {
+    size_t index = pair.second.index;
+    DCHECK(index < result.size());
+    DCHECK(!result[index]);
+    result[index] = pair.first;
+  }
+
+  return result;
+}
+
+std::vector<std::pair<const Target*, bool>>
+InheritedLibraries::GetOrderedAndPublicFlag() const {
+  std::vector<std::pair<const Target*, bool>> result;
+  result.resize(map_.size());
+
+  for (const auto& pair : map_) {
+    size_t index = pair.second.index;
+    DCHECK(index < result.size());
+    DCHECK(!result[index].first);
+    result[index] = std::make_pair(pair.first, pair.second.is_public);
+  }
+
+  return result;
+}
+
+void InheritedLibraries::Append(const Target* target, bool is_public) {
+  // Try to insert a new node.
+  auto insert_result =
+      map_.insert(std::make_pair(target, Node(map_.size(), is_public)));
+
+  if (!insert_result.second) {
+    // Element already present, insert failed and insert_result indicates the
+    // old one. The old one may need to have its public flag updated.
+    if (is_public) {
+      Node& existing_node = insert_result.first->second;
+      existing_node.is_public = true;
+    }
+  }
+}
+
+void InheritedLibraries::AppendInherited(const InheritedLibraries& other,
+                                         bool is_public) {
+  // Append all items in order, mark them public only if the're already public
+  // and we're adding them publicly.
+  for (const auto& cur : other.GetOrderedAndPublicFlag())
+    Append(cur.first, is_public && cur.second);
+}
+
+void InheritedLibraries::AppendPublicSharedLibraries(
+    const InheritedLibraries& other,
+    bool is_public) {
+  for (const auto& cur : other.GetOrderedAndPublicFlag()) {
+    if (cur.first->output_type() == Target::SHARED_LIBRARY && cur.second)
+      Append(cur.first, is_public);
+  }
+}
diff --git a/src/gn/inherited_libraries.h b/src/gn/inherited_libraries.h
new file mode 100644 (file)
index 0000000..f56c648
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_INHERITED_LIBRARIES_H_
+#define TOOLS_GN_INHERITED_LIBRARIES_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+
+class Target;
+
+// Represents an ordered uniquified set of all shared/static libraries for
+// a given target. These are pushed up the dependency tree.
+//
+// Maintaining the order is important so GN links all libraries in the same
+// order specified in the build files.
+//
+// Since this list is uniquified, appending to the list will not actually
+// append a new item if the target already exists. However, the existing one
+// may have its is_public flag updated. "Public" always wins, so is_public will
+// be true if any dependency with that name has been set to public.
+class InheritedLibraries {
+ public:
+  InheritedLibraries();
+  ~InheritedLibraries();
+
+  // Returns the list of dependencies in order, optionally with the flag
+  // indicating whether the dependency is public.
+  std::vector<const Target*> GetOrdered() const;
+  std::vector<std::pair<const Target*, bool>> GetOrderedAndPublicFlag() const;
+
+  // Adds a single dependency to the end of the list. See note on adding above.
+  void Append(const Target* target, bool is_public);
+
+  // Appends all items from the "other" list to the current one. The is_public
+  // parameter indicates how the current target depends on the items in
+  // "other". If is_public is true, the existing public flags of the appended
+  // items will be preserved (propagating the public-ness up the dependency
+  // chain). If is_public is false, all deps will be added as private since
+  // the current target isn't forwarding them.
+  void AppendInherited(const InheritedLibraries& other, bool is_public);
+
+  // Like AppendInherited but only appends the items in "other" that are of
+  // type SHARED_LIBRARY and only when they're marked public. This is used
+  // to push shared libraries up the dependency chain, following only public
+  // deps, to dependent targets that need to use them.
+  void AppendPublicSharedLibraries(const InheritedLibraries& other,
+                                   bool is_public);
+
+ private:
+  struct Node {
+    Node() : index(static_cast<size_t>(-1)), is_public(false) {}
+    Node(size_t i, bool p) : index(i), is_public(p) {}
+
+    size_t index;
+    bool is_public;
+  };
+
+  using LibraryMap = std::map<const Target*, Node>;
+  LibraryMap map_;
+
+  DISALLOW_COPY_AND_ASSIGN(InheritedLibraries);
+};
+
+#endif  // TOOLS_GN_INHERITED_LIBRARIES_H_
diff --git a/src/gn/inherited_libraries_unittest.cc b/src/gn/inherited_libraries_unittest.cc
new file mode 100644 (file)
index 0000000..bd77426
--- /dev/null
@@ -0,0 +1,135 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/inherited_libraries.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+// In these tests, Pair can't be used conveniently because the
+// "const" won't be inferred and the types won't match. This helper makes the
+// right type of pair with the const Target.
+std::pair<const Target*, bool> Pair(const Target* t, bool b) {
+  return std::pair<const Target*, bool>(t, b);
+}
+
+}  // namespace
+
+TEST(InheritedLibraries, Unique) {
+  TestWithScope setup;
+
+  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
+  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
+
+  // Setup, add the two targets as private.
+  InheritedLibraries libs;
+  libs.Append(&a, false);
+  libs.Append(&b, false);
+  auto result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(Pair(&a, false), result[0]);
+  EXPECT_EQ(Pair(&b, false), result[1]);
+
+  // Add again as private, this should be a NOP.
+  libs.Append(&a, false);
+  libs.Append(&b, false);
+  result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(Pair(&a, false), result[0]);
+  EXPECT_EQ(Pair(&b, false), result[1]);
+
+  // Add as public, this should make both public.
+  libs.Append(&a, true);
+  libs.Append(&b, true);
+  result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(Pair(&a, true), result[0]);
+  EXPECT_EQ(Pair(&b, true), result[1]);
+
+  // Add again private, they should stay public.
+  libs.Append(&a, false);
+  libs.Append(&b, false);
+  result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(2u, result.size());
+  EXPECT_EQ(Pair(&a, true), result[0]);
+  EXPECT_EQ(Pair(&b, true), result[1]);
+}
+
+TEST(InheritedLibraries, AppendInherited) {
+  TestWithScope setup;
+
+  Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
+  Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
+  Target w(setup.settings(), Label(SourceDir("//foo/"), "w"));
+  Target x(setup.settings(), Label(SourceDir("//foo/"), "x"));
+  Target y(setup.settings(), Label(SourceDir("//foo/"), "y"));
+  Target z(setup.settings(), Label(SourceDir("//foo/"), "z"));
+
+  InheritedLibraries libs;
+  libs.Append(&a, false);
+  libs.Append(&b, false);
+
+  // Appending these things with private inheritance should make them private,
+  // no matter how they're listed in the appended class.
+  InheritedLibraries append_private;
+  append_private.Append(&a, true);
+  append_private.Append(&b, false);
+  append_private.Append(&w, true);
+  append_private.Append(&x, false);
+  libs.AppendInherited(append_private, false);
+
+  auto result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(4u, result.size());
+  EXPECT_EQ(Pair(&a, false), result[0]);
+  EXPECT_EQ(Pair(&b, false), result[1]);
+  EXPECT_EQ(Pair(&w, false), result[2]);
+  EXPECT_EQ(Pair(&x, false), result[3]);
+
+  // Appending these things with public inheritance should convert them.
+  InheritedLibraries append_public;
+  append_public.Append(&a, true);
+  append_public.Append(&b, false);
+  append_public.Append(&y, true);
+  append_public.Append(&z, false);
+  libs.AppendInherited(append_public, true);
+
+  result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(6u, result.size());
+  EXPECT_EQ(Pair(&a, true), result[0]);  // Converted to public.
+  EXPECT_EQ(Pair(&b, false), result[1]);
+  EXPECT_EQ(Pair(&w, false), result[2]);
+  EXPECT_EQ(Pair(&x, false), result[3]);
+  EXPECT_EQ(Pair(&y, true), result[4]);  // Appended as public.
+  EXPECT_EQ(Pair(&z, false), result[5]);
+}
+
+TEST(InheritedLibraries, AppendPublicSharedLibraries) {
+  TestWithScope setup;
+  InheritedLibraries append;
+
+  // Two source sets.
+  Target set_pub(setup.settings(), Label(SourceDir("//foo/"), "set_pub"));
+  set_pub.set_output_type(Target::SOURCE_SET);
+  append.Append(&set_pub, true);
+  Target set_priv(setup.settings(), Label(SourceDir("//foo/"), "set_priv"));
+  set_priv.set_output_type(Target::SOURCE_SET);
+  append.Append(&set_priv, false);
+
+  // Two shared libraries.
+  Target sh_pub(setup.settings(), Label(SourceDir("//foo/"), "sh_pub"));
+  sh_pub.set_output_type(Target::SHARED_LIBRARY);
+  append.Append(&sh_pub, true);
+  Target sh_priv(setup.settings(), Label(SourceDir("//foo/"), "sh_priv"));
+  sh_priv.set_output_type(Target::SHARED_LIBRARY);
+  append.Append(&sh_priv, false);
+
+  InheritedLibraries libs;
+  libs.AppendPublicSharedLibraries(append, true);
+
+  auto result = libs.GetOrderedAndPublicFlag();
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(Pair(&sh_pub, true), result[0]);
+}
diff --git a/src/gn/input_conversion.cc b/src/gn/input_conversion.cc
new file mode 100644 (file)
index 0000000..e82a939
--- /dev/null
@@ -0,0 +1,359 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/input_conversion.h"
+
+#include <iterator>
+#include <memory>
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/input_file.h"
+#include "gn/label.h"
+#include "gn/parse_tree.h"
+#include "gn/parser.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/tokenizer.h"
+#include "gn/value.h"
+
+namespace {
+
+enum ValueOrScope {
+  PARSE_VALUE,  // Treat the input as an expression.
+  PARSE_SCOPE,  // Treat the input as code and return the resulting scope.
+};
+
+// Sets the origin of the value and any nested values with the given node.
+Value ParseValueOrScope(const Settings* settings,
+                        const std::string& input,
+                        ValueOrScope what,
+                        const ParseNode* origin,
+                        Err* err) {
+  // The memory for these will be kept around by the input file manager
+  // so the origin parse nodes for the values will be preserved.
+  InputFile* input_file;
+  std::vector<Token>* tokens;
+  std::unique_ptr<ParseNode>* parse_root_ptr;
+  g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file,
+                                                     &tokens, &parse_root_ptr);
+
+  input_file->SetContents(input);
+  if (origin) {
+    // This description will be the blame for any error messages caused by
+    // script parsing or if a value is blamed. It will say
+    // "Error at <...>:line:char" so here we try to make a string for <...>
+    // that reads well in this context.
+    input_file->set_friendly_name("dynamically parsed input that " +
+                                  origin->GetRange().begin().Describe(true) +
+                                  " loaded ");
+  } else {
+    input_file->set_friendly_name("dynamic input");
+  }
+
+  *tokens = Tokenizer::Tokenize(input_file, err);
+  if (err->has_error())
+    return Value();
+
+  // Parse the file according to what we're looking for.
+  if (what == PARSE_VALUE)
+    *parse_root_ptr = Parser::ParseValue(*tokens, err);
+  else
+    *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block.
+  if (err->has_error())
+    return Value();
+  ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below.
+
+  // It's valid for the result to be a null pointer, this just means that the
+  // script returned nothing.
+  if (!parse_root)
+    return Value();
+
+  std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings);
+  Value result = parse_root->Execute(scope.get(), err);
+  if (err->has_error())
+    return Value();
+
+  // When we want the result as a scope, the result is actually the scope
+  // we made, rather than the result of running the block (which will be empty).
+  if (what == PARSE_SCOPE) {
+    DCHECK(result.type() == Value::NONE);
+    result = Value(origin, std::move(scope));
+  }
+  return result;
+}
+
+Value ParseList(const std::string& input, const ParseNode* origin, Err* err) {
+  Value ret(origin, Value::LIST);
+  std::vector<std::string> as_lines = base::SplitString(
+      input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  // Trim one empty line from the end since the last line might end in a
+  // newline. If the user wants more trimming, they'll specify "trim" in the
+  // input conversion options.
+  if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
+    as_lines.resize(as_lines.size() - 1);
+
+  ret.list_value().reserve(as_lines.size());
+  for (const auto& line : as_lines)
+    ret.list_value().push_back(Value(origin, line));
+  return ret;
+}
+
+bool IsIdentifier(const std::string_view& buffer) {
+  DCHECK(buffer.size() > 0);
+  if (!Tokenizer::IsIdentifierFirstChar(buffer[0]))
+    return false;
+  for (size_t i = 1; i < buffer.size(); i++)
+    if (!Tokenizer::IsIdentifierContinuingChar(buffer[i]))
+      return false;
+  return true;
+}
+
+Value ParseJSONValue(const Settings* settings,
+                     const base::Value& value,
+                     const ParseNode* origin,
+                     InputFile* input_file,
+                     Err* err) {
+  switch (value.type()) {
+    case base::Value::Type::NONE:
+      *err = Err(origin, "Null values are not supported.");
+      return Value();
+    case base::Value::Type::BOOLEAN:
+      return Value(origin, value.GetBool());
+    case base::Value::Type::INTEGER:
+      return Value(origin, static_cast<int64_t>(value.GetInt()));
+    case base::Value::Type::STRING:
+      return Value(origin, value.GetString());
+    case base::Value::Type::BINARY:
+      *err = Err(origin, "Binary values are not supported.");
+      return Value();
+    case base::Value::Type::DICTIONARY: {
+      std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings);
+      for (auto it : value.DictItems()) {
+        Value parsed_value =
+            ParseJSONValue(settings, it.second, origin, input_file, err);
+        if (!IsIdentifier(it.first)) {
+          *err = Err(origin, "Invalid identifier \"" + it.first + "\".");
+          return Value();
+        }
+        // Search for the key in the input file. We know it's present because
+        // it was parsed by the JSON reader, but we need its location to
+        // construct a std::string_view that can be used as key in the Scope.
+        size_t off = input_file->contents().find("\"" + it.first + "\"");
+        if (off == std::string::npos) {
+          *err = Err(origin, "Invalid encoding \"" + it.first + "\".");
+          return Value();
+        }
+        std::string_view key(&input_file->contents()[off + 1], it.first.size());
+        scope->SetValue(key, std::move(parsed_value), origin);
+      }
+      return Value(origin, std::move(scope));
+    }
+    case base::Value::Type::LIST: {
+      Value result(origin, Value::LIST);
+      result.list_value().reserve(value.GetList().size());
+      for (const auto& val : value.GetList()) {
+        Value parsed_value =
+            ParseJSONValue(settings, val, origin, input_file, err);
+        result.list_value().push_back(parsed_value);
+      }
+      return result;
+    }
+  }
+  return Value();
+}
+
+// Parses the JSON string and converts it to GN value.
+Value ParseJSON(const Settings* settings,
+                const std::string& input,
+                const ParseNode* origin,
+                Err* err) {
+  InputFile* input_file;
+  std::vector<Token>* tokens;
+  std::unique_ptr<ParseNode>* parse_root_ptr;
+  g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file,
+                                                     &tokens, &parse_root_ptr);
+  input_file->SetContents(input);
+
+  int error_code_out;
+  std::string error_msg_out;
+  std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
+      input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out,
+      &error_msg_out);
+  if (!value) {
+    *err = Err(origin, "Input is not a valid JSON: " + error_msg_out);
+    return Value();
+  }
+
+  return ParseJSONValue(settings, *value, origin, input_file, err);
+}
+
+// Backend for ConvertInputToValue, this takes the extracted string for the
+// input conversion so we can recursively call ourselves to handle the optional
+// "trim" prefix. This original value is also kept for the purposes of throwing
+// errors.
+Value DoConvertInputToValue(const Settings* settings,
+                            const std::string& input,
+                            const ParseNode* origin,
+                            const Value& original_input_conversion,
+                            const std::string& input_conversion,
+                            Err* err) {
+  if (input_conversion.empty())
+    return Value();  // Empty string means discard the result.
+
+  const char kTrimPrefix[] = "trim ";
+  if (base::StartsWith(input_conversion, kTrimPrefix,
+                       base::CompareCase::SENSITIVE)) {
+    std::string trimmed;
+    base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
+
+    // Remove "trim" prefix from the input conversion and re-run.
+    return DoConvertInputToValue(
+        settings, trimmed, origin, original_input_conversion,
+        input_conversion.substr(std::size(kTrimPrefix) - 1), err);
+  }
+
+  if (input_conversion == "value")
+    return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
+  if (input_conversion == "string")
+    return Value(origin, input);
+  if (input_conversion == "list lines")
+    return ParseList(input, origin, err);
+  if (input_conversion == "scope")
+    return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
+  if (input_conversion == "json")
+    return ParseJSON(settings, input, origin, err);
+
+  *err = Err(original_input_conversion, "Not a valid input_conversion.",
+             "Run gn help input_conversion to see your options.");
+  return Value();
+}
+
+}  // namespace
+
+const char kInputOutputConversion_Help[] =
+    R"(Input and output conversion
+
+  Input and output conversions are arguments to file and process functions
+  that specify how to convert data to or from external formats. The possible
+  values for parameters specifying conversions are:
+
+  "" (the default)
+      input: Discard the result and return None.
+
+      output: If value is a list, then "list lines"; otherwise "value".
+
+  "list lines"
+      input:
+        Return the file contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line may or may
+        not end in a newline.
+
+        After splitting, each individual line will be trimmed of whitespace on
+        both ends.
+
+      output:
+        Renders the value contents as a list, with a string for each line. The
+        newlines will not be present in the result. The last line will end in
+        with a newline.
+
+  "scope"
+      input:
+        Execute the block as GN code and return a scope with the resulting
+        values in it. If the input was:
+          a = [ "hello.cc", "world.cc" ]
+          b = 26
+        and you read the result into a variable named "val", then you could
+        access contents the "." operator on "val":
+          sources = val.a
+          some_count = val.b
+
+      output:
+        Renders the value contents as a GN code block, reversing the input
+        result above.
+
+  "string"
+      input: Return the file contents into a single string.
+
+      output:
+        Render the value contents into a single string. The output is:
+        a string renders with quotes, e.g. "str"
+        an integer renders as a stringified integer, e.g. "6"
+        a boolean renders as the associated string, e.g. "true"
+        a list renders as a representation of its contents, e.g. "[\"str\", 6]"
+        a scope renders as a GN code block of its values. If the Value was:
+            Value val;
+            val.a = [ "hello.cc", "world.cc" ];
+            val.b = 26
+          the resulting output would be:
+            "{
+                a = [ \"hello.cc\", \"world.cc\" ]
+                b = 26
+            }"
+
+  "value"
+      input:
+        Parse the input as if it was a literal rvalue in a buildfile. Examples of
+        typical program output using this mode:
+          [ "foo", "bar" ]     (result will be a list)
+        or
+          "foo bar"            (result will be a string)
+        or
+          5                    (result will be an integer)
+
+        Note that if the input is empty, the result will be a null value which
+        will produce an error if assigned to a variable.
+
+      output:
+        Render the value contents as a literal rvalue. Strings render with
+        escaped quotes.
+
+  "json"
+      input: Parse the input as a JSON and convert it to equivalent GN rvalue.
+
+      output: Convert the Value to equivalent JSON value.
+
+      The data type mapping is:
+        a string in JSON maps to string in GN
+        an integer in JSON maps to integer in GN
+        a float in JSON is unsupported and will result in an error
+        an object in JSON maps to scope in GN
+        an array in JSON maps to list in GN
+        a boolean in JSON maps to boolean in GN
+        a null in JSON is unsupported and will result in an error
+
+      Nota that the input dictionary keys have to be valid GN identifiers
+      otherwise they will produce an error.
+
+  "trim ..." (input only)
+      Prefixing any of the other transformations with the word "trim" will
+      result in whitespace being trimmed from the beginning and end of the
+      result before processing.
+
+      Examples: "trim string" or "trim list lines"
+
+      Note that "trim value" is useless because the value parser skips
+      whitespace anyway.
+)";
+
+Value ConvertInputToValue(const Settings* settings,
+                          const std::string& input,
+                          const ParseNode* origin,
+                          const Value& input_conversion_value,
+                          Err* err) {
+  if (input_conversion_value.type() == Value::NONE)
+    return Value();  // Allow null inputs to mean discard the result.
+  if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
+    return Value();
+  return DoConvertInputToValue(settings, input, origin, input_conversion_value,
+                               input_conversion_value.string_value(), err);
+}
diff --git a/src/gn/input_conversion.h b/src/gn/input_conversion.h
new file mode 100644 (file)
index 0000000..6e09eb4
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_INPUT_CONVERSION_H_
+#define TOOLS_GN_INPUT_CONVERSION_H_
+
+#include <string>
+
+class Err;
+class ParseNode;
+class Settings;
+class Value;
+
+extern const char kInputOutputConversion_Help[];
+
+// Converts the given input string (is read from a file or output from a
+// script) to a Value. Conversions as specified in the input_conversion string
+// will be performed. The given origin will be used for constructing the
+// resulting Value.
+//
+// If the conversion string is invalid, the error will be set and an empty
+// value will be returned.
+Value ConvertInputToValue(const Settings* settings,
+                          const std::string& input,
+                          const ParseNode* origin,
+                          const Value& input_conversion_value,
+                          Err* err);
+
+#endif  // TOOLS_GN_INPUT_CONVERSION_H_
diff --git a/src/gn/input_conversion_unittest.cc b/src/gn/input_conversion_unittest.cc
new file mode 100644 (file)
index 0000000..054b337
--- /dev/null
@@ -0,0 +1,275 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/input_conversion.h"
+
+#include "gn/err.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+namespace {
+
+// InputConversion needs a global scheduler object.
+class InputConversionTest : public TestWithScheduler {
+ public:
+  InputConversionTest() = default;
+
+  const Settings* settings() { return setup_.settings(); }
+
+ private:
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(InputConversionTest, String) {
+  Err err;
+  std::string input("\nfoo bar  \n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "string"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::STRING, result.type());
+  EXPECT_EQ(input, result.string_value());
+
+  // Test with trimming.
+  result = ConvertInputToValue(settings(), input, nullptr,
+                               Value(nullptr, "trim string"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::STRING, result.type());
+  EXPECT_EQ("foo bar", result.string_value());
+}
+
+TEST_F(InputConversionTest, ListLines) {
+  Err err;
+  std::string input("\nfoo\nbar  \n\n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "list lines"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::LIST, result.type());
+  ASSERT_EQ(4u, result.list_value().size());
+  EXPECT_EQ("", result.list_value()[0].string_value());
+  EXPECT_EQ("foo", result.list_value()[1].string_value());
+  EXPECT_EQ("bar", result.list_value()[2].string_value());
+  EXPECT_EQ("", result.list_value()[3].string_value());
+
+  // Test with trimming.
+  result = ConvertInputToValue(settings(), input, nullptr,
+                               Value(nullptr, "trim list lines"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::LIST, result.type());
+  ASSERT_EQ(2u, result.list_value().size());
+  EXPECT_EQ("foo", result.list_value()[0].string_value());
+  EXPECT_EQ("bar", result.list_value()[1].string_value());
+}
+
+TEST_F(InputConversionTest, ValueString) {
+  Err err;
+  std::string input("\"str\"");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::STRING, result.type());
+  EXPECT_EQ("str", result.string_value());
+}
+
+TEST_F(InputConversionTest, ValueInt) {
+  Err err;
+  std::string input("\n\n  6 \n ");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::INTEGER, result.type());
+  EXPECT_EQ(6, result.int_value());
+}
+
+TEST_F(InputConversionTest, ValueList) {
+  Err err;
+  std::string input("\n [ \"a\", 5]");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(Value::LIST, result.type());
+  ASSERT_EQ(2u, result.list_value().size());
+  EXPECT_EQ("a", result.list_value()[0].string_value());
+  EXPECT_EQ(5, result.list_value()[1].int_value());
+}
+
+TEST_F(InputConversionTest, ValueDict) {
+  Err err;
+  std::string input("\n a = 5 b = \"foo\" c = a + 2");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "scope"), &err);
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(Value::SCOPE, result.type());
+
+  const Value* a_value = result.scope_value()->GetValue("a");
+  ASSERT_TRUE(a_value);
+  EXPECT_EQ(5, a_value->int_value());
+
+  const Value* b_value = result.scope_value()->GetValue("b");
+  ASSERT_TRUE(b_value);
+  EXPECT_EQ("foo", b_value->string_value());
+
+  const Value* c_value = result.scope_value()->GetValue("c");
+  ASSERT_TRUE(c_value);
+  EXPECT_EQ(7, c_value->int_value());
+
+  // Tests that when we get Values out of the input conversion, the resulting
+  // values have an origin set to something corresponding to the input.
+  const ParseNode* a_origin = a_value->origin();
+  ASSERT_TRUE(a_origin);
+  LocationRange a_range = a_origin->GetRange();
+  EXPECT_EQ(2, a_range.begin().line_number());
+  EXPECT_EQ(6, a_range.begin().column_number());
+
+  const InputFile* a_file = a_range.begin().file();
+  EXPECT_EQ(input, a_file->contents());
+}
+
+TEST_F(InputConversionTest, ValueJSON) {
+  Err err;
+  std::string input(R"*({
+  "a": 5,
+  "b": "foo",
+  "c": {
+    "d": true,
+    "e": [
+      {
+        "f": "bar"
+      }
+    ]
+  }
+})*");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "json"), &err);
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(Value::SCOPE, result.type());
+
+  const Value* a_value = result.scope_value()->GetValue("a");
+  ASSERT_TRUE(a_value);
+  EXPECT_EQ(5, a_value->int_value());
+
+  const Value* b_value = result.scope_value()->GetValue("b");
+  ASSERT_TRUE(b_value);
+  EXPECT_EQ("foo", b_value->string_value());
+
+  const Value* c_value = result.scope_value()->GetValue("c");
+  ASSERT_TRUE(c_value);
+  ASSERT_EQ(Value::SCOPE, c_value->type());
+
+  const Value* d_value = c_value->scope_value()->GetValue("d");
+  ASSERT_TRUE(d_value);
+  EXPECT_EQ(true, d_value->boolean_value());
+
+  const Value* e_value = c_value->scope_value()->GetValue("e");
+  ASSERT_TRUE(e_value);
+  ASSERT_EQ(Value::LIST, e_value->type());
+
+  EXPECT_EQ(1u, e_value->list_value().size());
+  ASSERT_EQ(Value::SCOPE, e_value->list_value()[0].type());
+  const Value* f_value = e_value->list_value()[0].scope_value()->GetValue("f");
+  ASSERT_TRUE(f_value);
+  EXPECT_EQ("bar", f_value->string_value());
+}
+
+TEST_F(InputConversionTest, ValueJSONInvalidInput) {
+  Err err;
+  std::string input(R"*({
+  "a": 5,
+  "b":
+})*");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "json"), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Input is not a valid JSON: Line: 4, column: 2, Unexpected token.",
+            err.message());
+}
+
+TEST_F(InputConversionTest, ValueJSONUnsupportedValue) {
+  Err err;
+  std::string input(R"*({
+  "a": null
+})*");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "json"), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Null values are not supported.", err.message());
+}
+
+TEST_F(InputConversionTest, ValueJSONInvalidVariable) {
+  Err err;
+  std::string input(R"*({
+  "a\\x0001b": 5
+})*");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "json"), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ("Invalid identifier \"a\\x0001b\".", err.message());
+}
+
+TEST_F(InputConversionTest, ValueJSONUnsupported) {
+  Err err;
+  std::string input(R"*({
+  "d": 0.0
+})*");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "json"), &err);
+  EXPECT_TRUE(err.has_error());
+  // Doubles aren't supported.
+  EXPECT_EQ("Input is not a valid JSON: ", err.message());
+}
+
+TEST_F(InputConversionTest, ValueEmpty) {
+  Err err;
+  Value result = ConvertInputToValue(settings(), "", nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::NONE, result.type());
+}
+
+TEST_F(InputConversionTest, ValueError) {
+  static const char* const kTests[] = {
+      "\n [ \"a\", 5\nfoo bar",
+
+      // Blocks not allowed.
+      "{ foo = 5 }",
+
+      // Function calls not allowed.
+      "print(5)",
+
+      // Trailing junk not allowed.
+      "233105-1",
+
+      // Non-literals hidden in arrays are not allowed.
+      "[233105 - 1]",
+      "[rebase_path(\"//\")]",
+  };
+
+  for (auto* test : kTests) {
+    Err err;
+    std::string input(test);
+    Value result = ConvertInputToValue(settings(), input, nullptr,
+                                       Value(nullptr, "value"), &err);
+    EXPECT_TRUE(err.has_error()) << test;
+  }
+}
+
+// Passing none or the empty string for input conversion should ignore the
+// result.
+TEST_F(InputConversionTest, Ignore) {
+  Err err;
+  Value result = ConvertInputToValue(settings(), "foo", nullptr, Value(), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::NONE, result.type());
+
+  result =
+      ConvertInputToValue(settings(), "foo", nullptr, Value(nullptr, ""), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(Value::NONE, result.type());
+}
diff --git a/src/gn/input_file.cc b/src/gn/input_file.cc
new file mode 100644 (file)
index 0000000..8c92c48
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/input_file.h"
+
+#include "base/files/file_util.h"
+
+InputFile::InputFile(const SourceFile& name)
+    : name_(name), dir_(name_.GetDir()) {}
+
+InputFile::~InputFile() = default;
+
+void InputFile::SetContents(const std::string& c) {
+  contents_loaded_ = true;
+  contents_ = c;
+}
+
+bool InputFile::Load(const base::FilePath& system_path) {
+  if (base::ReadFileToString(system_path, &contents_)) {
+    contents_loaded_ = true;
+    physical_name_ = system_path;
+    return true;
+  }
+  return false;
+}
diff --git a/src/gn/input_file.h b/src/gn/input_file.h
new file mode 100644 (file)
index 0000000..efb15bd
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_INPUT_FILE_H_
+#define TOOLS_GN_INPUT_FILE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+
+class InputFile {
+ public:
+  explicit InputFile(const SourceFile& name);
+
+  ~InputFile();
+
+  // The virtual name passed into the constructor. This does not take into
+  // account whether the file was loaded from the secondary source tree (see
+  // BuildSettings secondary_source_path).
+  const SourceFile& name() const { return name_; }
+
+  // The directory is just a cached version of name()->GetDir() but we get this
+  // a lot so computing it once up front saves a bunch of work.
+  const SourceDir& dir() const { return dir_; }
+
+  // The physical name tells the actual name on disk, if there is one.
+  const base::FilePath& physical_name() const { return physical_name_; }
+
+  // The friendly name can be set to override the name() in cases where there
+  // is no name (like SetContents is used instead) or if the name doesn't
+  // make sense. This will be displayed in error messages.
+  const std::string& friendly_name() const { return friendly_name_; }
+  void set_friendly_name(const std::string& f) { friendly_name_ = f; }
+
+  const std::string& contents() const {
+    DCHECK(contents_loaded_);
+    return contents_;
+  }
+
+  // For testing and in cases where this input doesn't actually refer to
+  // "a file".
+  void SetContents(const std::string& c);
+
+  // Loads the given file synchronously, returning true on success. This
+  bool Load(const base::FilePath& system_path);
+
+ private:
+  SourceFile name_;
+  SourceDir dir_;
+
+  base::FilePath physical_name_;
+  std::string friendly_name_;
+
+  bool contents_loaded_ = false;
+  std::string contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(InputFile);
+};
+
+#endif  // TOOLS_GN_INPUT_FILE_H_
diff --git a/src/gn/input_file_manager.cc b/src/gn/input_file_manager.cc
new file mode 100644 (file)
index 0000000..4ecb80b
--- /dev/null
@@ -0,0 +1,341 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/input_file_manager.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/parser.h"
+#include "gn/scheduler.h"
+#include "gn/scope_per_file_provider.h"
+#include "gn/tokenizer.h"
+#include "gn/trace.h"
+#include "gn/vector_utils.h"
+
+namespace {
+
+// The opposite of std::lock_guard.
+struct ScopedUnlock {
+  ScopedUnlock(std::unique_lock<std::mutex>& lock) : lock_(lock) {
+    lock_.unlock();
+  }
+  ~ScopedUnlock() { lock_.lock(); }
+
+ private:
+  std::unique_lock<std::mutex>& lock_;
+};
+
+void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb,
+                            const ParseNode* node) {
+  cb(node);
+}
+
+bool DoLoadFile(const LocationRange& origin,
+                const BuildSettings* build_settings,
+                const SourceFile& name,
+                InputFileManager::SyncLoadFileCallback load_file_callback,
+                InputFile* file,
+                std::vector<Token>* tokens,
+                std::unique_ptr<ParseNode>* root,
+                Err* err) {
+  // Do all of this stuff outside the lock. We should not give out file
+  // pointers until the read is complete.
+  if (g_scheduler->verbose_logging()) {
+    std::string logmsg = name.value();
+    if (origin.begin().file())
+      logmsg += " (referenced from " + origin.begin().Describe(false) + ")";
+    g_scheduler->Log("Loading", logmsg);
+  }
+
+  // Read.
+  base::FilePath primary_path = build_settings->GetFullPath(name);
+  ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, name.value());
+  if (load_file_callback) {
+    if (!load_file_callback(name, file)) {
+      *err = Err(origin, "Can't load input file.",
+                 "File not mocked by load_file_callback:\n  " + name.value());
+      return false;
+    }
+  } else if (!file->Load(primary_path)) {
+    if (!build_settings->secondary_source_path().empty()) {
+      // Fall back to secondary source tree.
+      base::FilePath secondary_path =
+          build_settings->GetFullPathSecondary(name);
+      if (!file->Load(secondary_path)) {
+        *err = Err(origin, "Can't load input file.",
+                   "Unable to load:\n  " + FilePathToUTF8(primary_path) +
+                       "\n"
+                       "I also checked in the secondary tree for:\n  " +
+                       FilePathToUTF8(secondary_path));
+        return false;
+      }
+    } else {
+      *err = Err(origin,
+                 "Unable to load \"" + FilePathToUTF8(primary_path) + "\".");
+      return false;
+    }
+  }
+  load_trace.Done();
+
+  ScopedTrace exec_trace(TraceItem::TRACE_FILE_PARSE, name.value());
+
+  // Tokenize.
+  *tokens = Tokenizer::Tokenize(file, err);
+  if (err->has_error())
+    return false;
+
+  // Parse.
+  *root = Parser::Parse(*tokens, err);
+  if (err->has_error())
+    return false;
+
+  exec_trace.Done();
+  return true;
+}
+
+}  // namespace
+
+InputFileManager::InputFileData::InputFileData(const SourceFile& file_name)
+    : file(file_name), loaded(false), sync_invocation(false) {}
+
+InputFileManager::InputFileData::~InputFileData() = default;
+
+InputFileManager::InputFileManager() = default;
+
+InputFileManager::~InputFileManager() {
+  // Should be single-threaded by now.
+}
+
+bool InputFileManager::AsyncLoadFile(const LocationRange& origin,
+                                     const BuildSettings* build_settings,
+                                     const SourceFile& file_name,
+                                     const FileLoadCallback& callback,
+                                     Err* err) {
+  // Try not to schedule callbacks while holding the lock. All cases that don't
+  // want to schedule should return early. Otherwise, this will be scheduled
+  // after we leave the lock.
+  std::function<void()> schedule_this;
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    InputFileMap::const_iterator found = input_files_.find(file_name);
+    if (found == input_files_.end()) {
+      // New file, schedule load.
+      std::unique_ptr<InputFileData> data =
+          std::make_unique<InputFileData>(file_name);
+      data->scheduled_callbacks.push_back(callback);
+      schedule_this = [this, origin, build_settings, file_name,
+                       file = &data->file]() {
+        BackgroundLoadFile(origin, build_settings, file_name, file);
+      };
+      input_files_[file_name] = std::move(data);
+
+    } else {
+      InputFileData* data = found->second.get();
+
+      // Prevent mixing async and sync loads. See SyncLoadFile for discussion.
+      if (data->sync_invocation) {
+        g_scheduler->FailWithError(Err(
+            origin, "Load type mismatch.",
+            "The file \"" + file_name.value() +
+                "\" was previously loaded\n"
+                "synchronously (via an import) and now you're trying to load "
+                "it "
+                "asynchronously\n(via a deps rule). This is a class 2 "
+                "misdemeanor: "
+                "a single input file must\nbe loaded the same way each time to "
+                "avoid blowing my tiny, tiny mind."));
+        return false;
+      }
+
+      if (data->loaded) {
+        // Can just directly issue the callback on the background thread.
+        schedule_this = [callback, root = data->parsed_root.get()]() {
+          InvokeFileLoadCallback(callback, root);
+        };
+      } else {
+        // Load is pending on this file, schedule the invoke.
+        data->scheduled_callbacks.push_back(callback);
+        return true;
+      }
+    }
+  }
+  g_scheduler->ScheduleWork(std::move(schedule_this));
+  return true;
+}
+
+const ParseNode* InputFileManager::SyncLoadFile(
+    const LocationRange& origin,
+    const BuildSettings* build_settings,
+    const SourceFile& file_name,
+    Err* err) {
+  std::unique_lock<std::mutex> lock(lock_);
+
+  InputFileData* data = nullptr;
+  InputFileMap::iterator found = input_files_.find(file_name);
+  if (found == input_files_.end()) {
+    // Haven't seen this file yet, start loading right now.
+    std::unique_ptr<InputFileData> new_data =
+        std::make_unique<InputFileData>(file_name);
+    data = new_data.get();
+    data->sync_invocation = true;
+    input_files_[file_name] = std::move(new_data);
+
+    ScopedUnlock unlock(lock);
+    if (!LoadFile(origin, build_settings, file_name, &data->file, err))
+      return nullptr;
+  } else {
+    // This file has either been loaded or is pending loading.
+    data = found->second.get();
+
+    if (!data->sync_invocation) {
+      // Don't allow mixing of sync and async loads. If an async load is
+      // scheduled and then a bunch of threads need to load it synchronously
+      // and block on it loading, it could deadlock or at least cause a lot
+      // of wasted CPU while those threads wait for the load to complete (which
+      // may be far back in the input queue).
+      //
+      // We could work around this by promoting the load to a sync load. This
+      // requires a bunch of extra code to either check flags and likely do
+      // extra locking (bad) or to just do both types of load on the file and
+      // deal with the race condition.
+      //
+      // I have no practical way to test this, and generally we should have
+      // all include files processed synchronously and all build files
+      // processed asynchronously, so it doesn't happen in practice.
+      *err = Err(origin, "Load type mismatch.",
+                 "The file \"" + file_name.value() +
+                     "\" was previously loaded\n"
+                     "asynchronously (via a deps rule) and now you're trying "
+                     "to load it "
+                     "synchronously.\nThis is a class 2 misdemeanor: a single "
+                     "input file "
+                     "must be loaded the same way\neach time to avoid blowing "
+                     "my tiny, "
+                     "tiny mind.");
+      return nullptr;
+    }
+
+    if (!data->loaded) {
+      // Wait for the already-pending sync load to complete.
+      if (!data->completion_event) {
+        data->completion_event = std::make_unique<AutoResetEvent>();
+      }
+      {
+        ScopedUnlock unlock(lock);
+        data->completion_event->Wait();
+      }
+      // If there were multiple waiters on the same event, we now need to wake
+      // up the next one.
+      data->completion_event->Signal();
+    }
+  }
+
+  // The other load could have failed. It is possible that this thread's error
+  // will be reported to the scheduler before the other thread's (and the first
+  // error reported "wins"). Forward the parse error from the other load for
+  // this thread so that the error message is useful.
+  if (!data->parsed_root)
+    *err = data->parse_error;
+  return data->parsed_root.get();
+}
+
+void InputFileManager::AddDynamicInput(
+    const SourceFile& name,
+    InputFile** file,
+    std::vector<Token>** tokens,
+    std::unique_ptr<ParseNode>** parse_root) {
+  std::unique_ptr<InputFileData> data = std::make_unique<InputFileData>(name);
+  *file = &data->file;
+  *tokens = &data->tokens;
+  *parse_root = &data->parsed_root;
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+    dynamic_inputs_.push_back(std::move(data));
+  }
+}
+
+int InputFileManager::GetInputFileCount() const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return static_cast<int>(input_files_.size());
+}
+
+void InputFileManager::AddAllPhysicalInputFileNamesToVectorSetSorter(
+    VectorSetSorter<base::FilePath>* sorter) const {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  for (const auto& file : input_files_) {
+    if (!file.second->file.physical_name().empty())
+      sorter->Add(file.second->file.physical_name());
+  }
+}
+
+void InputFileManager::BackgroundLoadFile(const LocationRange& origin,
+                                          const BuildSettings* build_settings,
+                                          const SourceFile& name,
+                                          InputFile* file) {
+  Err err;
+  if (!LoadFile(origin, build_settings, name, file, &err))
+    g_scheduler->FailWithError(err);
+}
+
+bool InputFileManager::LoadFile(const LocationRange& origin,
+                                const BuildSettings* build_settings,
+                                const SourceFile& name,
+                                InputFile* file,
+                                Err* err) {
+  std::vector<Token> tokens;
+  std::unique_ptr<ParseNode> root;
+  bool success =
+      DoLoadFile(origin, build_settings, name, load_file_callback_, file, &tokens, &root, err);
+  // Can't return early. We have to ensure that the completion event is
+  // signaled in all cases because another thread could be blocked on this one.
+
+  // Save this pointer for running the callbacks below, which happens after the
+  // scoped ptr ownership is taken away inside the lock.
+  ParseNode* unowned_root = root.get();
+
+  std::vector<FileLoadCallback> callbacks;
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+    DCHECK(input_files_.find(name) != input_files_.end());
+
+    InputFileData* data = input_files_[name].get();
+    data->loaded = true;
+    if (success) {
+      data->tokens = std::move(tokens);
+      data->parsed_root = std::move(root);
+    } else {
+      data->parse_error = *err;
+    }
+
+    // Unblock waiters on this event.
+    //
+    // It's somewhat bad to signal this inside the lock. When it's used, it's
+    // lazily created inside the lock. So we need to do the check and signal
+    // inside the lock to avoid race conditions on the lazy creation of the
+    // lock.
+    //
+    // We could avoid this by creating the lock every time, but the lock is
+    // very seldom used and will generally be NULL, so my current theory is that
+    // several signals of a completion event inside a lock is better than
+    // creating about 1000 extra locks (one for each file).
+    if (data->completion_event)
+      data->completion_event->Signal();
+
+    callbacks = std::move(data->scheduled_callbacks);
+  }
+
+  // Run pending invocations. Theoretically we could schedule each of these
+  // separately to get some parallelism. But normally there will only be one
+  // item in the list, so that's extra overhead and complexity for no gain.
+  if (success) {
+    for (const auto& cb : callbacks)
+      cb(unowned_root);
+  }
+  return success;
+}
diff --git a/src/gn/input_file_manager.h b/src/gn/input_file_manager.h
new file mode 100644 (file)
index 0000000..d495bee
--- /dev/null
@@ -0,0 +1,175 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_INPUT_FILE_MANAGER_H_
+#define TOOLS_GN_INPUT_FILE_MANAGER_H_
+
+#include <functional>
+#include <mutex>
+#include <set>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/settings.h"
+#include "gn/vector_utils.h"
+#include "util/auto_reset_event.h"
+
+class BuildSettings;
+class Err;
+class LocationRange;
+class ParseNode;
+class Token;
+
+// Manages loading and parsing files from disk. This doesn't actually have
+// any context for executing the results, so potentially multiple configs
+// could use the same input file (saving parsing).
+//
+// This class is threadsafe.
+//
+// InputFile objects must never be deleted while the program is running since
+// various state points into them.
+class InputFileManager : public base::RefCountedThreadSafe<InputFileManager> {
+ public:
+  // Callback issued when a file is loaded. On auccess, the parse node will
+  // refer to the root block of the file. On failure, this will be NULL.
+  using FileLoadCallback = std::function<void(const ParseNode*)>;
+
+  // Callback to emulate SyncLoadFile in tests.
+  using SyncLoadFileCallback =
+      std::function<bool(const SourceFile& file_name, InputFile* file)>;
+
+  InputFileManager();
+
+  // Loads the given file and executes the callback on the worker pool.
+  //
+  // There are two types of errors. For errors known synchronously, the error
+  // will be set, it will return false, and no work will be scheduled.
+  //
+  // For parse errors and such that happen in the future, the error will be
+  // logged to the scheduler and the callback will be invoked with a null
+  // ParseNode pointer. The given |origin| will be blamed for the invocation.
+  bool AsyncLoadFile(const LocationRange& origin,
+                     const BuildSettings* build_settings,
+                     const SourceFile& file_name,
+                     const FileLoadCallback& callback,
+                     Err* err);
+
+  // Loads and parses the given file synchronously, returning the root block
+  // corresponding to the parsed result. On error, return NULL and the given
+  // Err is set.
+  const ParseNode* SyncLoadFile(const LocationRange& origin,
+                                const BuildSettings* build_settings,
+                                const SourceFile& file_name,
+                                Err* err);
+
+  // Creates an entry to manage the memory associated with keeping a parsed
+  // set of code in memory.
+  //
+  // The values pointed to by the parameters will be filled with pointers to
+  // the file, tokens, and parse node that this class created. The calling
+  // code is responsible for populating these values and maintaining
+  // threadsafety. This class' only job is to hold onto the memory and delete
+  // it when the program exits.
+  //
+  // This solves the problem that sometimes we need to execute something
+  // dynamic and save the result, but the values all have references to the
+  // nodes and file that created it. Either we need to reset the origin of
+  // the values and lose context for error reporting, or somehow keep the
+  // associated parse nodes, tokens, and file data in memory. This function
+  // allows the latter.
+  void AddDynamicInput(const SourceFile& name,
+                       InputFile** file,
+                       std::vector<Token>** tokens,
+                       std::unique_ptr<ParseNode>** parse_root);
+
+  // Does not count dynamic input.
+  int GetInputFileCount() const;
+
+  // Add all physical input files to a VectorSetSorter instance.
+  // This allows fast merging and sorting with other file paths sets.
+  //
+  // This is more memory efficient than returning a vector of base::FilePath
+  // instance, especially with projects with a very large number of input files,
+  // but note that the VectorSetSorter only holds pointers to the
+  // items recorded in this InputFileManager instance, and it is up to the
+  // caller to ensure these will not change until the sorter is destroyed.
+  void AddAllPhysicalInputFileNamesToVectorSetSorter(
+      VectorSetSorter<base::FilePath>* sorter) const;
+
+  void set_load_file_callback(SyncLoadFileCallback load_file_callback) {
+    load_file_callback_ = load_file_callback;
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<InputFileManager>;
+
+  struct InputFileData {
+    explicit InputFileData(const SourceFile& file_name);
+    ~InputFileData();
+
+    // Don't touch this outside the lock until it's marked loaded.
+    InputFile file;
+
+    bool loaded;
+
+    bool sync_invocation;
+
+    // Lists all invocations that need to be executed when the file completes
+    // loading.
+    std::vector<FileLoadCallback> scheduled_callbacks;
+
+    // Event to signal when the load is complete (or fails). This is lazily
+    // created only when a thread is synchronously waiting for this load (which
+    // only happens for imports).
+    std::unique_ptr<AutoResetEvent> completion_event;
+
+    std::vector<Token> tokens;
+
+    // Null before the file is loaded or if loading failed.
+    std::unique_ptr<ParseNode> parsed_root;
+    Err parse_error;
+  };
+
+  virtual ~InputFileManager();
+
+  void BackgroundLoadFile(const LocationRange& origin,
+                          const BuildSettings* build_settings,
+                          const SourceFile& name,
+                          InputFile* file);
+
+  // Loads the given file. On error, sets the Err and return false.
+  bool LoadFile(const LocationRange& origin,
+                const BuildSettings* build_settings,
+                const SourceFile& name,
+                InputFile* file,
+                Err* err);
+
+  mutable std::mutex lock_;
+
+  // Maps repo-relative filenames to the corresponding owned pointer.
+  using InputFileMap =
+      std::unordered_map<SourceFile, std::unique_ptr<InputFileData>>;
+  InputFileMap input_files_;
+
+  // Tracks all dynamic inputs. The data are holders for memory management
+  // purposes and should not be read or modified by this class. The values
+  // will be vended out to the code creating the dynamic input, who is in
+  // charge of the threadsafety requirements.
+  //
+  // See AddDynamicInput().
+  std::vector<std::unique_ptr<InputFileData>> dynamic_inputs_;
+
+  // Used by unit tests to mock out SyncLoadFile().
+  SyncLoadFileCallback load_file_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(InputFileManager);
+};
+
+#endif  // TOOLS_GN_INPUT_FILE_MANAGER_H_
diff --git a/src/gn/item.cc b/src/gn/item.cc
new file mode 100644 (file)
index 0000000..3a3a166
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/item.h"
+
+#include "base/logging.h"
+#include "gn/settings.h"
+
+Item::Item(const Settings* settings,
+           const Label& label,
+           const SourceFileSet& build_dependency_files)
+    : settings_(settings),
+      label_(label),
+      build_dependency_files_(build_dependency_files),
+      defined_from_(nullptr) {}
+
+Item::~Item() = default;
+
+Config* Item::AsConfig() {
+  return nullptr;
+}
+const Config* Item::AsConfig() const {
+  return nullptr;
+}
+Pool* Item::AsPool() {
+  return nullptr;
+}
+const Pool* Item::AsPool() const {
+  return nullptr;
+}
+Target* Item::AsTarget() {
+  return nullptr;
+}
+const Target* Item::AsTarget() const {
+  return nullptr;
+}
+Toolchain* Item::AsToolchain() {
+  return nullptr;
+}
+const Toolchain* Item::AsToolchain() const {
+  return nullptr;
+}
+
+std::string Item::GetItemTypeName() const {
+  if (AsConfig())
+    return "config";
+  if (AsTarget())
+    return "target";
+  if (AsToolchain())
+    return "toolchain";
+  if (AsPool())
+    return "pool";
+  NOTREACHED();
+  return "this thing that I have no idea what it is";
+}
+
+bool Item::OnResolved(Err* err) {
+  return true;
+}
diff --git a/src/gn/item.h b/src/gn/item.h
new file mode 100644 (file)
index 0000000..2942a66
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ITEM_H_
+#define TOOLS_GN_ITEM_H_
+
+#include <set>
+#include <string>
+
+#include "gn/label.h"
+#include "gn/source_file.h"
+#include "gn/visibility.h"
+
+class Config;
+class ParseNode;
+class Pool;
+class Settings;
+class SourceFile;
+class Target;
+class Toolchain;
+
+// A named item (target, config, etc.) that participates in the dependency
+// graph.
+class Item {
+ public:
+  Item(const Settings* settings,
+       const Label& label,
+       const SourceFileSet& build_dependency_files = {});
+  virtual ~Item();
+
+  const Settings* settings() const { return settings_; }
+
+  // This is guaranteed to never change after construction so this can be
+  // accessed from any thread with no locking once the item is constructed.
+  const Label& label() const { return label_; }
+
+  const ParseNode* defined_from() const { return defined_from_; }
+  void set_defined_from(const ParseNode* df) { defined_from_ = df; }
+
+  Visibility& visibility() { return visibility_; }
+  const Visibility& visibility() const { return visibility_; }
+
+  // Manual RTTI.
+  virtual Config* AsConfig();
+  virtual const Config* AsConfig() const;
+  virtual Pool* AsPool();
+  virtual const Pool* AsPool() const;
+  virtual Target* AsTarget();
+  virtual const Target* AsTarget() const;
+  virtual Toolchain* AsToolchain();
+  virtual const Toolchain* AsToolchain() const;
+
+  // Returns a name like "target" or "config" for the type of item this is, to
+  // be used in logging and error messages.
+  std::string GetItemTypeName() const;
+
+  // Returns the set of build files that may affect this item, please refer to
+  // Scope for how this is determined.
+  const SourceFileSet& build_dependency_files() const {
+    return build_dependency_files_;
+  }
+
+  SourceFileSet& build_dependency_files() { return build_dependency_files_; }
+
+  // Called when this item is resolved, meaning it and all of its dependents
+  // have no unresolved deps. Returns true on success. Sets the error and
+  // returns false on failure.
+  virtual bool OnResolved(Err* err);
+
+ private:
+  const Settings* settings_;
+  Label label_;
+  SourceFileSet build_dependency_files_;
+  const ParseNode* defined_from_;
+
+  Visibility visibility_;
+};
+
+#endif  // TOOLS_GN_ITEM_H_
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc
new file mode 100644 (file)
index 0000000..a8aad90
--- /dev/null
@@ -0,0 +1,514 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/json_project_writer.h"
+
+#include <algorithm>
+#include <fstream>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/json/string_escape.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/builder.h"
+#include "gn/commands.h"
+#include "gn/deps_iterator.h"
+#include "gn/desc_builder.h"
+#include "gn/exec_process.h"
+#include "gn/filesystem_utils.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/string_output_buffer.h"
+
+// Structure of JSON output file
+// {
+//   "build_settings" : {
+//     "root_path" : "absolute path of project root",
+//     "build_dir" : "build directory (project relative)",
+//     "default_toolchain" : "name of default toolchain"
+//   }
+//   "targets" : {
+//      "target x full label" : { target x properties },
+//      "target y full label" : { target y properties },
+//      ...
+//    }
+// }
+// See desc_builder.cc for overview of target properties
+
+namespace {
+
+void AddTargetDependencies(const Target* target,
+                           std::set<const Target*>* deps) {
+  for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+    if (deps->find(pair.ptr) == deps->end()) {
+      deps->insert(pair.ptr);
+      AddTargetDependencies(pair.ptr, deps);
+    }
+  }
+}
+
+// Filters targets according to filter string; Will also recursively
+// add dependent targets.
+bool FilterTargets(const BuildSettings* build_settings,
+                   std::vector<const Target*>& all_targets,
+                   std::vector<const Target*>* targets,
+                   const std::string& dir_filter_string,
+                   Err* err) {
+  if (dir_filter_string.empty()) {
+    *targets = all_targets;
+  } else {
+    targets->reserve(all_targets.size());
+    std::vector<LabelPattern> filters;
+    if (!commands::FilterPatternsFromString(build_settings, dir_filter_string,
+                                            &filters, err)) {
+      return false;
+    }
+    commands::FilterTargetsByPatterns(all_targets, filters, targets);
+
+    std::set<const Target*> target_set(targets->begin(), targets->end());
+    for (const auto* target : *targets)
+      AddTargetDependencies(target, &target_set);
+
+    targets->clear();
+    targets->insert(targets->end(), target_set.begin(), target_set.end());
+  }
+
+  // Sort the list of targets per-label to get a consistent ordering of them
+  // in the generated project (and thus stability of the file generated).
+  std::sort(targets->begin(), targets->end(),
+            [](const Target* a, const Target* b) {
+              return a->label().name() < b->label().name();
+            });
+
+  return true;
+}
+
+bool InvokePython(const BuildSettings* build_settings,
+                  const base::FilePath& python_script_path,
+                  const std::string& python_script_extra_args,
+                  const base::FilePath& output_path,
+                  bool quiet,
+                  Err* err) {
+  const base::FilePath& python_path = build_settings->python_path();
+  base::CommandLine cmdline(python_path);
+  cmdline.AppendArg("--");
+  cmdline.AppendArgPath(python_script_path);
+  cmdline.AppendArgPath(output_path);
+  if (!python_script_extra_args.empty()) {
+    cmdline.AppendArg(python_script_extra_args);
+  }
+  base::FilePath startup_dir =
+      build_settings->GetFullPath(build_settings->build_dir());
+
+  std::string output;
+  std::string stderr_output;
+
+  int exit_code = 0;
+  if (!internal::ExecProcess(cmdline, startup_dir, &output, &stderr_output,
+                             &exit_code)) {
+    *err =
+        Err(Location(), "Could not execute python.",
+            "I was trying to execute \"" + FilePathToUTF8(python_path) + "\".");
+    return false;
+  }
+
+  if (!quiet) {
+    printf("%s", output.c_str());
+    fprintf(stderr, "%s", stderr_output.c_str());
+  }
+
+  if (exit_code != 0) {
+    *err = Err(Location(), "Python has quit with exit code " +
+                               base::IntToString(exit_code) + ".");
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+bool JSONProjectWriter::RunAndWriteFiles(
+    const BuildSettings* build_settings,
+    const Builder& builder,
+    const std::string& file_name,
+    const std::string& exec_script,
+    const std::string& exec_script_extra_args,
+    const std::string& dir_filter_string,
+    bool quiet,
+    Err* err) {
+  SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
+      Value(nullptr, file_name), err);
+  if (output_file.is_null()) {
+    return false;
+  }
+
+  base::FilePath output_path = build_settings->GetFullPath(output_file);
+
+  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+  std::vector<const Target*> targets;
+  if (!FilterTargets(build_settings, all_targets, &targets, dir_filter_string,
+                     err)) {
+    return false;
+  }
+
+  StringOutputBuffer json = GenerateJSON(build_settings, targets);
+  if (!json.ContentsEqual(output_path)) {
+    if (!json.WriteToFile(output_path, err)) {
+      return false;
+    }
+
+    if (!exec_script.empty()) {
+      SourceFile script_file;
+      if (exec_script[0] != '/') {
+        // Relative path, assume the base is in build_dir.
+        script_file = build_settings->build_dir().ResolveRelativeFile(
+            Value(nullptr, exec_script), err);
+        if (script_file.is_null()) {
+          return false;
+        }
+      } else {
+        script_file = SourceFile(exec_script);
+      }
+      base::FilePath script_path = build_settings->GetFullPath(script_file);
+      return InvokePython(build_settings, script_path, exec_script_extra_args,
+                          output_path, quiet, err);
+    }
+  }
+
+  return true;
+}
+
+namespace {
+
+// NOTE: Intentional macro definition allows compile-time string concatenation.
+// (see usage below).
+#if defined(OS_WINDOWS)
+#define LINE_ENDING "\r\n"
+#else
+#define LINE_ENDING "\n"
+#endif
+
+// Helper class to output a, potentially very large, JSON file to a
+// StringOutputBuffer. Note that sorting the keys, if desired, is left to
+// the user (unlike base::JSONWriter). This allows rendering to be performed
+// in series of incremental steps. Usage is:
+//
+//   1) Create instance, passing a StringOutputBuffer reference as the
+//      destination.
+//
+//   2) Add keys and values using one of the following:
+//
+//       a) AddString(key, string_value) to add one string value.
+//
+//       b) BeginList(key), AddListItem(), EndList() to add a string list.
+//          NOTE: Only lists of strings are supported here.
+//
+//       c) BeginDict(key), ... add other keys, followed by EndDict() to add
+//          a dictionary key.
+//
+//   3) Call Close() or destroy the instance to finalize the output.
+//
+class SimpleJSONWriter {
+ public:
+  // Constructor.
+  SimpleJSONWriter(StringOutputBuffer& out) : out_(out) {
+    out_ << "{" LINE_ENDING;
+    SetIndentation(1u);
+  }
+
+  // Destructor.
+  ~SimpleJSONWriter() { Close(); }
+
+  // Closing finalizes the output.
+  void Close() {
+    if (indentation_ > 0) {
+      DCHECK(indentation_ == 1u);
+      if (comma_.size())
+        out_ << LINE_ENDING;
+
+      out_ << "}" LINE_ENDING;
+      SetIndentation(0);
+    }
+  }
+
+  // Add new string-valued key.
+  void AddString(std::string_view key, std::string_view value) {
+    if (comma_.size()) {
+      out_ << comma_;
+    }
+    AddMargin() << Escape(key) << ": " << Escape(value);
+    comma_ = "," LINE_ENDING;
+  }
+
+  // Begin a new list. Must be followed by zero or more AddListItem() calls,
+  // then by EndList().
+  void BeginList(std::string_view key) {
+    if (comma_.size())
+      out_ << comma_;
+    AddMargin() << Escape(key) << ": [ ";
+    comma_ = {};
+  }
+
+  // Add a new list item. For now only string values are supported.
+  void AddListItem(std::string_view item) {
+    if (comma_.size())
+      out_ << comma_;
+    out_ << Escape(item);
+    comma_ = ", ";
+  }
+
+  // End current list.
+  void EndList() {
+    out_ << " ]";
+    comma_ = "," LINE_ENDING;
+  }
+
+  // Begin new dictionaary. Must be followed by zero or more other key
+  // additions, then a call to EndDict().
+  void BeginDict(std::string_view key) {
+    if (comma_.size())
+      out_ << comma_;
+
+    AddMargin() << Escape(key) << ": {";
+    SetIndentation(indentation_ + 1);
+    comma_ = LINE_ENDING;
+  }
+
+  // End current dictionary.
+  void EndDict() {
+    if (comma_.size())
+      out_ << LINE_ENDING;
+
+    SetIndentation(indentation_ - 1);
+    AddMargin() << "}";
+    comma_ = "," LINE_ENDING;
+  }
+
+  // Add a dictionary-valued key, whose value is already formatted as a valid
+  // JSON string. Useful to insert the output of base::JSONWriter::Write()
+  // into the target buffer.
+  void AddJSONDict(std::string_view key, std::string_view json) {
+    if (comma_.size())
+      out_ << comma_;
+    AddMargin() << Escape(key) << ": ";
+    if (json.empty()) {
+      out_ << "{ }";
+    } else {
+      DCHECK(json[0] == '{');
+      bool first_line = true;
+      do {
+        size_t line_end = json.find('\n');
+
+        // NOTE: Do not add margin if original input line is empty.
+        // This needs to deal with CR/LF which are part of |json| on Windows
+        // only, due to the way base::JSONWriter::Write() is implemented.
+        bool line_empty = (line_end == 0 || (line_end == 1 && json[0] == '\r'));
+        if (!first_line && !line_empty)
+          AddMargin();
+
+        if (line_end == std::string_view::npos) {
+          out_ << json;
+          ;
+          comma_ = {};
+          return;
+        }
+        // Important: do not add the final newline.
+        out_ << json.substr(
+            0, (line_end == json.size() - 1) ? line_end : line_end + 1);
+        json.remove_prefix(line_end + 1);
+        first_line = false;
+      } while (!json.empty());
+    }
+    comma_ = "," LINE_ENDING;
+  }
+
+ private:
+  // Return the JSON-escape version of |str|.
+  static std::string Escape(std::string_view str) {
+    std::string result;
+    base::EscapeJSONString(str, true, &result);
+    return result;
+  }
+
+  // Adjust indentation level.
+  void SetIndentation(size_t indentation) { indentation_ = indentation; }
+
+  // Append margin, and return reference to output buffer.
+  StringOutputBuffer& AddMargin() const {
+    static const char kMargin[17] = "                ";
+    size_t margin_len = indentation_ * 3;
+    while (margin_len > 0) {
+      size_t span = (margin_len > 16u) ? 16u : margin_len;
+      out_.Append(kMargin, span);
+      margin_len -= span;
+    }
+    return out_;
+  }
+
+  size_t indentation_ = 0;
+  std::string_view comma_;
+  StringOutputBuffer& out_;
+};
+
+}  // namespace
+
+StringOutputBuffer JSONProjectWriter::GenerateJSON(
+    const BuildSettings* build_settings,
+    std::vector<const Target*>& all_targets) {
+  Label default_toolchain_label;
+  if (!all_targets.empty())
+    default_toolchain_label =
+        all_targets[0]->settings()->default_toolchain_label();
+
+  StringOutputBuffer out;
+
+  // Sort the targets according to their human visible labels first.
+  std::unordered_map<const Target*, std::string> target_labels;
+  for (const Target* target : all_targets) {
+    target_labels[target] =
+        target->label().GetUserVisibleName(default_toolchain_label);
+  }
+
+  std::vector<const Target*> sorted_targets(all_targets.begin(),
+                                            all_targets.end());
+  std::sort(sorted_targets.begin(), sorted_targets.end(),
+            [&target_labels](const Target* a, const Target* b) {
+              return target_labels[a] < target_labels[b];
+            });
+
+  SimpleJSONWriter json_writer(out);
+
+  // IMPORTANT: Keep the keys sorted when adding them to |json_writer|.
+
+  json_writer.BeginDict("build_settings");
+  {
+    json_writer.AddString("build_dir", build_settings->build_dir().value());
+
+    json_writer.AddString("default_toolchain",
+                          default_toolchain_label.GetUserVisibleName(false));
+
+    json_writer.BeginList("gen_input_files");
+
+    // Other files read by the build.
+    std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
+
+    const InputFileManager* input_file_manager =
+        g_scheduler->input_file_manager();
+
+    VectorSetSorter<base::FilePath> sorter(
+        input_file_manager->GetInputFileCount() + other_files.size());
+
+    input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter);
+
+    sorter.Add(other_files.begin(), other_files.end());
+
+    std::string build_path = FilePathToUTF8(build_settings->root_path());
+    auto item_callback = [&json_writer,
+                          &build_path](const base::FilePath& input_file) {
+      std::string file;
+      if (MakeAbsolutePathRelativeIfPossible(
+              build_path, FilePathToUTF8(input_file), &file)) {
+        json_writer.AddListItem(file);
+      }
+    };
+    sorter.IterateOver(item_callback);
+
+    json_writer.EndList();  // gen_input_files
+
+    json_writer.AddString("root_path", build_settings->root_path_utf8());
+  }
+  json_writer.EndDict();  // build_settings
+
+  std::map<Label, const Toolchain*> toolchains;
+  json_writer.BeginDict("targets");
+  {
+    for (const auto* target : sorted_targets) {
+      auto description =
+          DescBuilder::DescriptionForTarget(target, "", false, false, false);
+      // Outputs need to be asked for separately.
+      auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs",
+                                                       false, false, false);
+      base::DictionaryValue* outputs_value = nullptr;
+      if (outputs->GetDictionary("source_outputs", &outputs_value) &&
+          !outputs_value->empty()) {
+        description->MergeDictionary(outputs.get());
+      }
+
+      std::string json_dict;
+      base::JSONWriter::WriteWithOptions(*description.get(),
+                                         base::JSONWriter::OPTIONS_PRETTY_PRINT,
+                                         &json_dict);
+      json_writer.AddJSONDict(target_labels[target], json_dict);
+      toolchains[target->toolchain()->label()] = target->toolchain();
+    }
+  }
+  json_writer.EndDict();  // targets
+
+  json_writer.BeginDict("toolchains");
+  {
+    for (const auto& tool_chain_kv : toolchains) {
+      base::Value toolchain{base::Value::Type::DICTIONARY};
+      const auto& tools = tool_chain_kv.second->tools();
+      for (const auto& tool_kv : tools) {
+        base::Value tool_info{base::Value::Type::DICTIONARY};
+        auto setIfNotEmptry = [&](const auto& key, const auto& value) {
+          if (value.size())
+            tool_info.SetKey(key, base::Value{value});
+        };
+        auto setSubstitutionList = [&](const auto& key,
+                                       const SubstitutionList& list) {
+          if (list.list().empty())
+            return;
+          base::Value values{base::Value::Type::LIST};
+          for (const auto& v : list.list())
+            values.GetList().emplace_back(base::Value{v.AsString()});
+          tool_info.SetKey(key, std::move(values));
+        };
+        const auto& tool = tool_kv.second;
+        setIfNotEmptry("command", tool->command().AsString());
+        setIfNotEmptry("command_launcher", tool->command_launcher());
+        setIfNotEmptry("default_output_extension",
+                       tool->default_output_extension());
+        setIfNotEmptry("default_output_dir",
+                       tool->default_output_dir().AsString());
+        setIfNotEmptry("depfile", tool->depfile().AsString());
+        setIfNotEmptry("description", tool->description().AsString());
+        setIfNotEmptry("framework_switch", tool->framework_switch());
+        setIfNotEmptry("weak_framework_switch", tool->weak_framework_switch());
+        setIfNotEmptry("framework_dir_switch", tool->framework_dir_switch());
+        setIfNotEmptry("lib_switch", tool->lib_switch());
+        setIfNotEmptry("lib_dir_switch", tool->lib_dir_switch());
+        setIfNotEmptry("linker_arg", tool->linker_arg());
+        setSubstitutionList("outputs", tool->outputs());
+        setSubstitutionList("partial_outputs", tool->partial_outputs());
+        setSubstitutionList("runtime_outputs", tool->runtime_outputs());
+        setIfNotEmptry("output_prefix", tool->output_prefix());
+
+        toolchain.SetKey(tool_kv.first, std::move(tool_info));
+      }
+      std::string json_dict;
+      base::JSONWriter::WriteWithOptions(
+          toolchain, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_dict);
+      json_writer.AddJSONDict(tool_chain_kv.first.GetUserVisibleName(false), json_dict);
+    }
+  }
+  json_writer.EndDict();  // toolchains
+
+  json_writer.Close();
+
+  return out;
+}
+
+std::string JSONProjectWriter::RenderJSON(
+    const BuildSettings* build_settings,
+    std::vector<const Target*>& all_targets) {
+  StringOutputBuffer storage = GenerateJSON(build_settings, all_targets);
+  return storage.str();
+}
diff --git a/src/gn/json_project_writer.h b/src/gn/json_project_writer.h
new file mode 100644 (file)
index 0000000..74293a4
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_JSON_WRITER_H_
+#define TOOLS_GN_JSON_WRITER_H_
+
+#include "gn/err.h"
+#include "gn/target.h"
+
+class Builder;
+class BuildSettings;
+class StringOutputBuffer;
+
+class JSONProjectWriter {
+ public:
+  static bool RunAndWriteFiles(const BuildSettings* build_setting,
+                               const Builder& builder,
+                               const std::string& file_name,
+                               const std::string& exec_script,
+                               const std::string& exec_script_extra_args,
+                               const std::string& dir_filter_string,
+                               bool quiet,
+                               Err* err);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(JSONWriter, ActionWithResponseFile);
+  FRIEND_TEST_ALL_PREFIXES(JSONWriter, ForEachWithResponseFile);
+  FRIEND_TEST_ALL_PREFIXES(JSONWriter, RustTarget);
+
+  static StringOutputBuffer GenerateJSON(
+      const BuildSettings* build_settings,
+      std::vector<const Target*>& all_targets);
+
+  static std::string RenderJSON(const BuildSettings* build_settings,
+                                std::vector<const Target*>& all_targets);
+};
+
+#endif
diff --git a/src/gn/json_project_writer_unittest.cc b/src/gn/json_project_writer_unittest.cc
new file mode 100644 (file)
index 0000000..a22511b
--- /dev/null
@@ -0,0 +1,719 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/json_project_writer.h"
+#include "base/strings/string_util.h"
+#include "gn/substitution_list.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using JSONWriter = TestWithScheduler;
+
+TEST_F(JSONWriter, ActionWithResponseFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION);
+
+  target.sources().push_back(SourceFile("//foo/source1.txt"));
+  target.config_values().inputs().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Make sure we get interesting substitutions for both the args and the
+  // response file contents.
+  target.action_values().args() =
+      SubstitutionList::MakeForTest("{{response_file_name}}");
+  target.action_values().rsp_file_contents() =
+      SubstitutionList::MakeForTest("-j", "3");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/output1.out");
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+#if defined(OS_WIN)
+  base::FilePath root_path =
+      base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
+#else
+  base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
+#endif
+  setup.build_settings()->SetRootPath(root_path);
+  g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
+  g_scheduler->AddGenDependency(
+      root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
+  g_scheduler->AddGenDependency(
+      root_path.Append(FILE_PATH_LITERAL("build/BUILD.gn")));
+  std::string out =
+      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      R"_({
+   "build_settings": {
+      "build_dir": "//out/Debug/",
+      "default_toolchain": "//toolchain:default",
+      "gen_input_files": [ "//.gn", "//BUILD.gn", "//build/BUILD.gn" ],
+)_"
+#if defined(OS_WIN)
+      "      \"root_path\": \"c:/path/to/src\"\n"
+#else
+      "      \"root_path\": \"/path/to/src\"\n"
+#endif
+      R"_(   },
+   "targets": {
+      "//foo:bar()": {
+         "args": [ "{{response_file_name}}" ],
+         "deps": [  ],
+         "inputs": [ "//foo/input1.txt" ],
+         "metadata": {
+
+         },
+         "outputs": [ "//out/Debug/output1.out" ],
+         "public": "*",
+         "response_file_contents": [ "-j", "3" ],
+         "script": "//foo/script.py",
+         "sources": [ "//foo/source1.txt" ],
+         "testonly": false,
+         "toolchain": "",
+         "type": "action",
+         "visibility": [  ]
+      }
+   },
+   "toolchains": {
+      "//toolchain:default": {
+         "alink": {
+            "command": "ar {{output}} {{source}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "cc": {
+            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "compile_xcassets": {
+            "command": "touch {{output}}"
+         },
+         "copy": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "copy_bundle_data": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "cxx": {
+            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "command_launcher": "launcher",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "link": {
+            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objc": {
+            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objcxx": {
+            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "rust_bin": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ]
+         },
+         "rust_cdylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_dylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_macro": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_rlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".rlib",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_staticlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".a",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "solink": {
+            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "solink_module": {
+            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "stamp": {
+            "command": "touch {{output}}"
+         },
+         "swift": {
+            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
+            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         }
+      }
+   }
+}
+)_";
+  EXPECT_EQ(expected_json, out) << out;
+}
+
+TEST_F(JSONWriter, RustTarget) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  std::string out =
+      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      R"_({
+   "build_settings": {
+      "build_dir": "//out/Debug/",
+      "default_toolchain": "//toolchain:default",
+      "gen_input_files": [  ],
+      "root_path": ""
+   },
+   "targets": {
+      "//foo:bar()": {
+         "allow_circular_includes_from": [  ],
+         "check_includes": true,
+         "crate_name": "foo",
+         "crate_root": "//foo/lib.rs",
+         "deps": [  ],
+         "externs": {
+
+         },
+         "metadata": {
+
+         },
+         "outputs": [ "//out/Debug/obj/foo/libbar.rlib" ],
+         "public": "*",
+         "sources": [ "//foo/lib.rs" ],
+         "testonly": false,
+         "toolchain": "",
+         "type": "rust_library",
+         "visibility": [ "*" ]
+      }
+   },
+   "toolchains": {
+      "//toolchain:default": {
+         "alink": {
+            "command": "ar {{output}} {{source}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "cc": {
+            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "compile_xcassets": {
+            "command": "touch {{output}}"
+         },
+         "copy": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "copy_bundle_data": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "cxx": {
+            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "command_launcher": "launcher",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "link": {
+            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objc": {
+            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objcxx": {
+            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "rust_bin": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ]
+         },
+         "rust_cdylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_dylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_macro": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_rlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".rlib",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_staticlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".a",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "solink": {
+            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "solink_module": {
+            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "stamp": {
+            "command": "touch {{output}}"
+         },
+         "swift": {
+            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
+            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         }
+      }
+   }
+}
+)_";
+  EXPECT_EQ(expected_json, out) << out;
+}
+
+TEST_F(JSONWriter, ForEachWithResponseFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Make sure we get interesting substitutions for both the args and the
+  // response file contents.
+  target.action_values().args() = SubstitutionList::MakeForTest(
+      "{{source}}", "{{source_file_part}}", "{{response_file_name}}");
+  target.action_values().rsp_file_contents() =
+      SubstitutionList::MakeForTest("-j", "{{source_name_part}}");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+#if defined(OS_WIN)
+  base::FilePath root_path =
+      base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
+#else
+  base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
+#endif
+  setup.build_settings()->SetRootPath(root_path);
+  g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
+  g_scheduler->AddGenDependency(
+      root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
+  std::string out =
+      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      R"_({
+   "build_settings": {
+      "build_dir": "//out/Debug/",
+      "default_toolchain": "//toolchain:default",
+      "gen_input_files": [ "//.gn", "//BUILD.gn" ],
+)_"
+#if defined(OS_WIN)
+      "      \"root_path\": \"c:/path/to/src\"\n"
+#else
+      "      \"root_path\": \"/path/to/src\"\n"
+#endif
+      R"_(   },
+   "targets": {
+      "//foo:bar()": {
+         "args": [ "{{source}}", "{{source_file_part}}", "{{response_file_name}}" ],
+         "deps": [  ],
+         "metadata": {
+
+         },
+         "output_patterns": [ "//out/Debug/{{source_name_part}}.out" ],
+         "outputs": [ "//out/Debug/input1.out" ],
+         "public": "*",
+         "response_file_contents": [ "-j", "{{source_name_part}}" ],
+         "script": "//foo/script.py",
+         "source_outputs": {
+            "//foo/input1.txt": [ "input1.out" ]
+         },
+         "sources": [ "//foo/input1.txt" ],
+         "testonly": false,
+         "toolchain": "",
+         "type": "action_foreach",
+         "visibility": [  ]
+      }
+   },
+   "toolchains": {
+      "//toolchain:default": {
+         "alink": {
+            "command": "ar {{output}} {{source}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "cc": {
+            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "compile_xcassets": {
+            "command": "touch {{output}}"
+         },
+         "copy": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "copy_bundle_data": {
+            "command": "cp {{source}} {{output}}"
+         },
+         "cxx": {
+            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "command_launcher": "launcher",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "link": {
+            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objc": {
+            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "objcxx": {
+            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "rust_bin": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ]
+         },
+         "rust_cdylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_dylib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "" ]
+         },
+         "rust_macro": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".so",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_rlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".rlib",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "rust_staticlib": {
+            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
+            "default_output_extension": ".a",
+            "framework_switch": "-lframework=",
+            "lib_dir_switch": "-Lnative=",
+            "lib_switch": "-l",
+            "linker_arg": "-Clink-arg=",
+            "output_prefix": "lib",
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
+         },
+         "solink": {
+            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "solink_module": {
+            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
+            "default_output_extension": ".so",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "output_prefix": "lib",
+            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
+            "weak_framework_switch": "-weak_framework "
+         },
+         "stamp": {
+            "command": "touch {{output}}"
+         },
+         "swift": {
+            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
+            "framework_dir_switch": "-F",
+            "framework_switch": "-framework ",
+            "lib_dir_switch": "-L",
+            "lib_switch": "-l",
+            "outputs": [ "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
+            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
+            "weak_framework_switch": "-weak_framework "
+         }
+      }
+   }
+}
+)_";
+  EXPECT_EQ(expected_json, out) << out;
+}
diff --git a/src/gn/label.cc b/src/gn/label.cc
new file mode 100644 (file)
index 0000000..173c3d5
--- /dev/null
@@ -0,0 +1,331 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/label.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/parse_tree.h"
+#include "gn/value.h"
+#include "util/build_config.h"
+
+namespace {
+
+// We print user visible label names with no trailing slash after the
+// directory name.
+std::string DirWithNoTrailingSlash(const SourceDir& dir) {
+  // Be careful not to trim if the input is just "/" or "//".
+  if (dir.value().size() > 2)
+    return dir.value().substr(0, dir.value().size() - 1);
+  return dir.value();
+}
+
+// Given the separate-out input (everything before the colon) in the dep rule,
+// computes the final build rule. Sets err on failure. On success,
+// |*used_implicit| will be set to whether the implicit current directory was
+// used. The value is used only for generating error messages.
+bool ComputeBuildLocationFromDep(const Value& input_value,
+                                 const SourceDir& current_dir,
+                                 const std::string_view& source_root,
+                                 const std::string_view& input,
+                                 SourceDir* result,
+                                 Err* err) {
+  // No rule, use the current location.
+  if (input.empty()) {
+    *result = current_dir;
+    return true;
+  }
+
+  *result =
+      current_dir.ResolveRelativeDir(input_value, input, err, source_root);
+  return true;
+}
+
+// Given the separated-out target name (after the colon) computes the final
+// name, using the implicit name from the previously-generated
+// computed_location if necessary. The input_value is used only for generating
+// error messages.
+bool ComputeTargetNameFromDep(const Value& input_value,
+                              const SourceDir& computed_location,
+                              const std::string_view& input,
+                              StringAtom* result,
+                              Err* err) {
+  if (!input.empty()) {
+    // Easy case: input is specified, just use it.
+    *result = StringAtom(input);
+    return true;
+  }
+
+  const std::string& loc = computed_location.value();
+
+  // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc.
+  if (loc.size() <= 2) {
+    *err = Err(input_value, "This dependency name is empty");
+    return false;
+  }
+
+  size_t next_to_last_slash = loc.rfind('/', loc.size() - 2);
+  DCHECK(next_to_last_slash != std::string::npos);
+  *result = StringAtom(std::string_view{&loc[next_to_last_slash + 1],
+                                        loc.size() - next_to_last_slash - 2});
+  return true;
+}
+
+// The original value is used only for error reporting, use the |input| as the
+// input to this function (which may be a substring of the original value when
+// we're parsing toolchains.
+//
+// If the output toolchain vars are NULL, then we'll report an error if we
+// find a toolchain specified (this is used when recursively parsing toolchain
+// labels which themselves can't have toolchain specs).
+//
+// We assume that the output variables are initialized to empty so we don't
+// write them unless we need them to contain something.
+//
+// Returns true on success. On failure, the out* variables might be written to
+// but shouldn't be used.
+bool Resolve(const SourceDir& current_dir,
+             const std::string_view& source_root,
+             const Label& current_toolchain,
+             const Value& original_value,
+             const std::string_view& input,
+             SourceDir* out_dir,
+             StringAtom* out_name,
+             SourceDir* out_toolchain_dir,
+             StringAtom* out_toolchain_name,
+             Err* err) {
+  // To workaround the problem that std::string_view operator[] doesn't return a
+  // ref.
+  const char* input_str = input.data();
+  size_t offset = 0;
+#if defined(OS_WIN)
+  if (IsPathAbsolute(input)) {
+    size_t drive_letter_pos = input[0] == '/' ? 1 : 0;
+    if (input.size() > drive_letter_pos + 2 &&
+        input[drive_letter_pos + 1] == ':' &&
+        IsSlash(input[drive_letter_pos + 2]) &&
+        base::IsAsciiAlpha(input[drive_letter_pos])) {
+      // Skip over the drive letter colon.
+      offset = drive_letter_pos + 2;
+    }
+  }
+#endif
+  size_t path_separator = input.find_first_of(":(", offset);
+  std::string_view location_piece;
+  std::string_view name_piece;
+  std::string_view toolchain_piece;
+  if (path_separator == std::string::npos) {
+    location_piece = input;
+    // Leave name & toolchain piece null.
+  } else {
+    location_piece = std::string_view(&input_str[0], path_separator);
+
+    size_t toolchain_separator = input.find('(', path_separator);
+    if (toolchain_separator == std::string::npos) {
+      name_piece = std::string_view(&input_str[path_separator + 1],
+                                    input.size() - path_separator - 1);
+      // Leave location piece null.
+    } else if (!out_toolchain_dir) {
+      // Toolchain specified but not allows in this context.
+      *err =
+          Err(original_value, "Toolchain has a toolchain.",
+              "Your toolchain definition (inside the parens) seems to itself "
+              "have a\ntoolchain. Don't do this.");
+      return false;
+    } else {
+      // Name piece is everything between the two separators. Note that the
+      // separators may be the same (e.g. "//foo(bar)" which means empty name.
+      if (toolchain_separator > path_separator) {
+        name_piece = std::string_view(&input_str[path_separator + 1],
+                                      toolchain_separator - path_separator - 1);
+      }
+
+      // Toolchain name should end in a ) and this should be the end of the
+      // string.
+      if (input[input.size() - 1] != ')') {
+        *err =
+            Err(original_value, "Bad toolchain name.",
+                "Toolchain name must end in a \")\" at the end of the label.");
+        return false;
+      }
+
+      // Subtract off the two parens to just get the toolchain name.
+      toolchain_piece =
+          std::string_view(&input_str[toolchain_separator + 1],
+                           input.size() - toolchain_separator - 2);
+    }
+  }
+
+  // Everything before the separator is the filename.
+  // We allow three cases:
+  //   Absolute:                "//foo:bar" -> /foo:bar
+  //   Target in current file:  ":foo"     -> <currentdir>:foo
+  //   Path with implicit name: "/foo"     -> /foo:foo
+  if (location_piece.empty() && name_piece.empty()) {
+    // Can't use both implicit filename and name (":").
+    *err = Err(original_value, "This doesn't specify a dependency.");
+    return false;
+  }
+
+  if (!ComputeBuildLocationFromDep(original_value, current_dir, source_root,
+                                   location_piece, out_dir, err))
+    return false;
+
+  if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece, out_name,
+                                err))
+    return false;
+
+  // Last, do the toolchains.
+  if (out_toolchain_dir) {
+    // Handle empty toolchain strings. We don't allow normal labels to be
+    // empty so we can't allow the recursive call of this function to do this
+    // check.
+    if (toolchain_piece.empty()) {
+      *out_toolchain_dir = current_toolchain.dir();
+      *out_toolchain_name = current_toolchain.name_atom();
+      return true;
+    } else {
+      return Resolve(current_dir, source_root, current_toolchain,
+                     original_value, toolchain_piece, out_toolchain_dir,
+                     out_toolchain_name, nullptr, nullptr, err);
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+const char kLabels_Help[] =
+    R"*(About labels
+
+  Everything that can participate in the dependency graph (targets, configs,
+  and toolchains) are identified by labels. A common label looks like:
+
+    //base/test:test_support
+
+  This consists of a source-root-absolute path, a colon, and a name. This means
+  to look for the thing named "test_support" in "base/test/BUILD.gn".
+
+  You can also specify system absolute paths if necessary. Typically such
+  paths would be specified via a build arg so the developer can specify where
+  the component is on their system.
+
+    /usr/local/foo:bar    (Posix)
+    /C:/Program Files/MyLibs:bar   (Windows)
+
+Toolchains
+
+  A canonical label includes the label of the toolchain being used. Normally,
+  the toolchain label is implicitly inherited from the current execution
+  context, but you can override this to specify cross-toolchain dependencies:
+
+    //base/test:test_support(//build/toolchain/win:msvc)
+
+  Here GN will look for the toolchain definition called "msvc" in the file
+  "//build/toolchain/win" to know how to compile this target.
+
+Relative labels
+
+  If you want to refer to something in the same buildfile, you can omit
+  the path name and just start with a colon. This format is recommended for
+  all same-file references.
+
+    :base
+
+  Labels can be specified as being relative to the current directory.
+  Stylistically, we prefer to use absolute paths for all non-file-local
+  references unless a build file needs to be run in different contexts (like a
+  project needs to be both standalone and pulled into other projects in
+  difference places in the directory hierarchy).
+
+    source/plugin:myplugin
+    ../net:url_request
+
+Implicit names
+
+  If a name is unspecified, it will inherit the directory name. Stylistically,
+  we prefer to omit the colon and name when possible:
+
+    //net  ->  //net:net
+    //tools/gn  ->  //tools/gn:gn
+)*";
+
+Label::Label() : hash_(ComputeHash()) {}
+
+Label::Label(const SourceDir& dir,
+             const std::string_view& name,
+             const SourceDir& toolchain_dir,
+             const std::string_view& toolchain_name)
+    : dir_(dir),
+      name_(StringAtom(name)),
+      toolchain_dir_(toolchain_dir),
+      toolchain_name_(StringAtom(toolchain_name)),
+      hash_(ComputeHash()) {}
+
+Label::Label(const SourceDir& dir, const std::string_view& name)
+    : dir_(dir), name_(StringAtom(name)),
+      hash_(ComputeHash()) {}
+
+// static
+Label Label::Resolve(const SourceDir& current_dir,
+                     const std::string_view& source_root,
+                     const Label& current_toolchain,
+                     const Value& input,
+                     Err* err) {
+  Label ret;
+  if (input.type() != Value::STRING) {
+    *err = Err(input, "Dependency is not a string.");
+    return ret;
+  }
+  const std::string& input_string = input.string_value();
+  if (input_string.empty()) {
+    *err = Err(input, "Dependency string is empty.");
+    return ret;
+  }
+
+  if (!::Resolve(current_dir, source_root, current_toolchain, input,
+                 input_string, &ret.dir_, &ret.name_, &ret.toolchain_dir_,
+                 &ret.toolchain_name_, err))
+    return Label();
+  return ret;
+}
+
+Label Label::GetToolchainLabel() const {
+  return Label(toolchain_dir_, toolchain_name_);
+}
+
+Label Label::GetWithNoToolchain() const {
+  return Label(dir_, name_);
+}
+
+std::string Label::GetUserVisibleName(bool include_toolchain) const {
+  std::string ret;
+  ret.reserve(dir_.value().size() + name_.str().size() + 1);
+
+  if (dir_.is_null())
+    return ret;
+
+  ret = DirWithNoTrailingSlash(dir_);
+  ret.push_back(':');
+  ret.append(name_.str());
+
+  if (include_toolchain) {
+    ret.push_back('(');
+    if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
+      ret.append(DirWithNoTrailingSlash(toolchain_dir_));
+      ret.push_back(':');
+      ret.append(toolchain_name_.str());
+    }
+    ret.push_back(')');
+  }
+  return ret;
+}
+
+std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
+  bool include_toolchain = default_toolchain.dir() != toolchain_dir_ ||
+                           default_toolchain.name_atom() != toolchain_name_;
+  return GetUserVisibleName(include_toolchain);
+}
diff --git a/src/gn/label.h b/src/gn/label.h
new file mode 100644 (file)
index 0000000..dc9813b
--- /dev/null
@@ -0,0 +1,143 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LABEL_H_
+#define TOOLS_GN_LABEL_H_
+
+#include <tuple>
+
+#include <stddef.h>
+
+#include "gn/source_dir.h"
+#include "gn/string_atom.h"
+
+class Err;
+class Value;
+
+// A label represents the name of a target or some other named thing in
+// the source path. The label is always absolute and always includes a name
+// part, so it starts with a slash, and has one colon.
+class Label {
+ public:
+  Label();
+
+  // Makes a label given an already-separated out path and name.
+  // See also Resolve().
+  Label(const SourceDir& dir,
+        const std::string_view& name,
+        const SourceDir& toolchain_dir,
+        const std::string_view& toolchain_name);
+
+  // Makes a label with an empty toolchain.
+  Label(const SourceDir& dir, const std::string_view& name);
+
+  // Resolves a string from a build file that may be relative to the
+  // current directory into a fully qualified label. On failure returns an
+  // is_null() label and sets the error.
+  static Label Resolve(const SourceDir& current_dir,
+                       const std::string_view& source_root,
+                       const Label& current_toolchain,
+                       const Value& input,
+                       Err* err);
+
+  bool is_null() const { return dir_.is_null(); }
+
+  const SourceDir& dir() const { return dir_; }
+  const std::string& name() const { return name_.str(); }
+  StringAtom name_atom() const { return name_; }
+
+  const SourceDir& toolchain_dir() const { return toolchain_dir_; }
+  const std::string& toolchain_name() const { return toolchain_name_.str(); }
+  StringAtom toolchain_name_atom() const { return toolchain_name_; }
+
+  // Returns the current label's toolchain as its own Label.
+  Label GetToolchainLabel() const;
+
+  // Returns a copy of this label but with an empty toolchain.
+  Label GetWithNoToolchain() const;
+
+  // Formats this label in a way that we can present to the user or expose to
+  // other parts of the system. SourceDirs end in slashes, but the user
+  // expects names like "//chrome/renderer:renderer_config" when printed. The
+  // toolchain is optionally included.
+  std::string GetUserVisibleName(bool include_toolchain) const;
+
+  // Like the above version, but automatically includes the toolchain if it's
+  // not the default one. Normally the user only cares about the toolchain for
+  // non-default ones, so this can make certain output more clear.
+  std::string GetUserVisibleName(const Label& default_toolchain) const;
+
+  bool operator==(const Label& other) const {
+    return name_.SameAs(other.name_) && dir_ == other.dir_ &&
+           toolchain_dir_ == other.toolchain_dir_ &&
+           toolchain_name_.SameAs(other.toolchain_name_);
+  }
+  bool operator!=(const Label& other) const { return !operator==(other); }
+  bool operator<(const Label& other) const {
+    // This custom comparison function uses the fact that SourceDir and
+    // StringAtom values have very fast equality comparison to avoid
+    // un-necessary string comparisons when components are equal.
+    if (dir_ != other.dir_)
+      return dir_ < other.dir_;
+
+    if (!name_.SameAs(other.name_))
+      return name_ < other.name_;
+
+    if (toolchain_dir_ != other.toolchain_dir_)
+      return toolchain_dir_ < other.toolchain_dir_;
+
+    return toolchain_name_ < other.toolchain_name_;
+  }
+
+  // Returns true if the toolchain dir/name of this object matches some
+  // other object.
+  bool ToolchainsEqual(const Label& other) const {
+    return toolchain_dir_ == other.toolchain_dir_ &&
+           toolchain_name_.SameAs(other.toolchain_name_);
+  }
+
+  size_t hash() const { return hash_; }
+
+ private:
+  Label(SourceDir dir, StringAtom name) : dir_(dir), name_(name) {}
+
+  Label(SourceDir dir,
+        StringAtom name,
+        SourceDir toolchain_dir,
+        StringAtom toolchain_name)
+      : dir_(dir),
+        name_(name),
+        toolchain_dir_(toolchain_dir),
+        toolchain_name_(toolchain_name) {}
+
+  size_t ComputeHash() const {
+    size_t h0 = dir_.hash();
+    size_t h1 = name_.hash();
+    size_t h2 = toolchain_dir_.hash();
+    size_t h3 = toolchain_name_.hash();
+    return ((h3 * 131 + h2) * 131 + h1) * 131 + h0;
+  }
+
+  SourceDir dir_;
+  StringAtom name_;
+
+  SourceDir toolchain_dir_;
+  StringAtom toolchain_name_;
+
+  size_t hash_;
+  // NOTE: Must be initialized by constructors with ComputeHash() value.
+};
+
+namespace std {
+
+template <>
+struct hash<Label> {
+  std::size_t operator()(const Label& v) const { return v.hash(); }
+};
+
+}  // namespace std
+
+extern const char kLabels_Help[];
+
+#endif  // TOOLS_GN_LABEL_H_
diff --git a/src/gn/label_pattern.cc b/src/gn/label_pattern.cc
new file mode 100644 (file)
index 0000000..35235e5
--- /dev/null
@@ -0,0 +1,275 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/label_pattern.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_util.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/value.h"
+#include "util/build_config.h"
+
+const char kLabelPattern_Help[] =
+    R"*(Label patterns
+
+  A label pattern is a way of expressing one or more labels in a portion of the
+  source tree. They are not general regular expressions.
+
+  They can take the following forms only:
+
+   - Explicit (no wildcard):
+       "//foo/bar:baz"
+       ":baz"
+
+   - Wildcard target names:
+       "//foo/bar:*" (all targets in the //foo/bar/BUILD.gn file)
+       ":*"  (all targets in the current build file)
+
+   - Wildcard directory names ("*" is only supported at the end)
+       "*"  (all targets)
+       "//foo/bar/*"  (all targets in any subdir of //foo/bar)
+       "./*"  (all targets in the current build file or sub dirs)
+
+  Any of the above forms can additionally take an explicit toolchain
+  in parenthesis at the end of the label pattern. In this case, the
+  toolchain must be fully qualified (no wildcards are supported in the
+  toolchain name).
+
+    "//foo:bar(//build/toolchain:mac)"
+        An explicit target in an explicit toolchain.
+
+    ":*(//build/toolchain/linux:32bit)"
+        All targets in the current build file using the 32-bit Linux toolchain.
+
+    "//foo/*(//build/toolchain:win)"
+        All targets in //foo and any subdirectory using the Windows
+        toolchain.
+)*";
+
+LabelPattern::LabelPattern() : type_(MATCH) {}
+
+LabelPattern::LabelPattern(Type type,
+                           const SourceDir& dir,
+                           const std::string_view& name,
+                           const Label& toolchain_label)
+    : toolchain_(toolchain_label), type_(type), dir_(dir), name_(name) {}
+
+LabelPattern::LabelPattern(const LabelPattern& other) = default;
+
+LabelPattern::~LabelPattern() = default;
+
+// static
+LabelPattern LabelPattern::GetPattern(const SourceDir& current_dir,
+                                      const std::string_view& source_root,
+                                      const Value& value,
+                                      Err* err) {
+  if (!value.VerifyTypeIs(Value::STRING, err))
+    return LabelPattern();
+
+  std::string_view str(value.string_value());
+  if (str.empty()) {
+    *err = Err(value, "Label pattern must not be empty.");
+    return LabelPattern();
+  }
+
+  // If there's no wildcard, this is specifying an exact label, use the
+  // label resolution code to get all the implicit name stuff.
+  size_t star = str.find('*');
+  if (star == std::string::npos) {
+    Label label = Label::Resolve(current_dir, source_root, Label(), value, err);
+    if (err->has_error())
+      return LabelPattern();
+
+    // Toolchain.
+    Label toolchain_label;
+    if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty())
+      toolchain_label = label.GetToolchainLabel();
+
+    return LabelPattern(MATCH, label.dir(), label.name(), toolchain_label);
+  }
+
+  // Wildcard case, need to split apart the label to see what it specifies.
+  Label toolchain_label;
+  size_t open_paren = str.find('(');
+  if (open_paren != std::string::npos) {
+    // Has a toolchain definition, extract inside the parens.
+    size_t close_paren = str.find(')', open_paren);
+    if (close_paren == std::string::npos) {
+      *err = Err(value, "No close paren when looking for toolchain name.");
+      return LabelPattern();
+    }
+
+    std::string toolchain_string(
+        str.substr(open_paren + 1, close_paren - open_paren - 1));
+    if (toolchain_string.find('*') != std::string::npos) {
+      *err = Err(value, "Can't have a wildcard in the toolchain.");
+      return LabelPattern();
+    }
+
+    // Parse the inside of the parens as a label for a toolchain.
+    Value value_for_toolchain(value.origin(), toolchain_string);
+    toolchain_label = Label::Resolve(current_dir, source_root, Label(),
+                                     value_for_toolchain, err);
+    if (err->has_error())
+      return LabelPattern();
+
+    // Trim off the toolchain for the processing below.
+    str = str.substr(0, open_paren);
+  }
+
+  // Extract path and name.
+  std::string_view path;
+  std::string_view name;
+  size_t offset = 0;
+#if defined(OS_WIN)
+  if (IsPathAbsolute(str)) {
+    size_t drive_letter_pos = str[0] == '/' ? 1 : 0;
+    if (str.size() > drive_letter_pos + 2 && str[drive_letter_pos + 1] == ':' &&
+        IsSlash(str[drive_letter_pos + 2]) &&
+        base::IsAsciiAlpha(str[drive_letter_pos])) {
+      // Skip over the drive letter colon.
+      offset = drive_letter_pos + 2;
+    }
+  }
+#endif
+  size_t colon = str.find(':', offset);
+  if (colon == std::string::npos) {
+    path = std::string_view(str);
+  } else {
+    path = str.substr(0, colon);
+    name = str.substr(colon + 1);
+  }
+
+  // The path can have these forms:
+  //   1. <empty>  (use current dir)
+  //   2. <non wildcard stuff>  (send through directory resolution)
+  //   3. <non wildcard stuff>*  (send stuff through dir resolution, note star)
+  //   4. *  (matches anything)
+  SourceDir dir;
+  bool has_path_star = false;
+  if (path.empty()) {
+    // Looks like ":foo".
+    dir = current_dir;
+  } else if (path[path.size() - 1] == '*') {
+    // Case 3 or 4 above.
+    has_path_star = true;
+
+    // Adjust path to contain everything but the star.
+    path = path.substr(0, path.size() - 1);
+
+    if (!path.empty() && path[path.size() - 1] != '/') {
+      // The input was "foo*" which is invalid.
+      *err =
+          Err(value, "'*' must match full directories in a label pattern.",
+              "You did \"foo*\" but this thing doesn't do general pattern\n"
+              "matching. Instead, you have to add a slash: \"foo/*\" to match\n"
+              "all targets in a directory hierarchy.");
+      return LabelPattern();
+    }
+  }
+
+  // Resolve the part of the path that's not the wildcard.
+  if (!path.empty()) {
+    // The non-wildcard stuff better not have a wildcard.
+    if (path.find('*') != std::string_view::npos) {
+      *err = Err(value, "Label patterns only support wildcard suffixes.",
+                 "The pattern contained a '*' that wasn't at the end.");
+      return LabelPattern();
+    }
+
+    // Resolve the non-wildcard stuff.
+    dir = current_dir.ResolveRelativeDir(value, path, err, source_root);
+    if (err->has_error())
+      return LabelPattern();
+  }
+
+  // Resolve the name. At this point, we're doing wildcard matches so the
+  // name should either be empty ("foo/*") or a wildcard ("foo:*");
+  if (colon != std::string::npos && name != "*") {
+    *err = Err(
+        value, "Invalid label pattern.",
+        "You seem to be using the wildcard more generally that is supported.\n"
+        "Did you mean \"foo:*\" to match everything in the file, or\n"
+        "\"./*\" to recursively match everything in the current subtree.");
+    return LabelPattern();
+  }
+
+  Type type;
+  if (has_path_star) {
+    // We know there's a wildcard, so if the name is empty it looks like
+    // "foo/*".
+    type = RECURSIVE_DIRECTORY;
+  } else {
+    // Everything else should be of the form "foo:*".
+    type = DIRECTORY;
+  }
+
+  // When we're doing wildcard matching, the name is always empty.
+  return LabelPattern(type, dir, std::string_view(), toolchain_label);
+}
+
+bool LabelPattern::HasWildcard(const std::string& str) {
+  // Just look for a star. In the future, we may want to handle escaping or
+  // other types of patterns.
+  return str.find('*') != std::string::npos;
+}
+
+bool LabelPattern::Matches(const Label& label) const {
+  if (!toolchain_.is_null()) {
+    // Toolchain must match exactly.
+    if (toolchain_.dir() != label.toolchain_dir() ||
+        toolchain_.name() != label.toolchain_name())
+      return false;
+  }
+
+  switch (type_) {
+    case MATCH:
+      return label.name() == name_ && label.dir() == dir_;
+    case DIRECTORY:
+      // The directories must match exactly.
+      return label.dir() == dir_;
+    case RECURSIVE_DIRECTORY:
+      // Our directory must be a prefix of the input label for recursive.
+      return label.dir().value().compare(0, dir_.value().size(),
+                                         dir_.value()) == 0;
+    default:
+      NOTREACHED();
+      return false;
+  }
+}
+
+// static
+bool LabelPattern::VectorMatches(const std::vector<LabelPattern>& patterns,
+                                 const Label& label) {
+  for (const auto& pattern : patterns) {
+    if (pattern.Matches(label))
+      return true;
+  }
+  return false;
+}
+
+std::string LabelPattern::Describe() const {
+  std::string result;
+
+  switch (type()) {
+    case MATCH:
+      result = DirectoryWithNoLastSlash(dir()) + ":" + name();
+      break;
+    case DIRECTORY:
+      result = DirectoryWithNoLastSlash(dir()) + ":*";
+      break;
+    case RECURSIVE_DIRECTORY:
+      result = dir().value() + "*";
+      break;
+  }
+
+  if (!toolchain_.is_null()) {
+    result.push_back('(');
+    result.append(toolchain_.GetUserVisibleName(false));
+    result.push_back(')');
+  }
+  return result;
+}
diff --git a/src/gn/label_pattern.h b/src/gn/label_pattern.h
new file mode 100644 (file)
index 0000000..f40750e
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LABEL_PATTERN_H_
+#define TOOLS_GN_LABEL_PATTERN_H_
+
+#include <string_view>
+
+#include "gn/label.h"
+#include "gn/source_dir.h"
+
+class Err;
+class Value;
+
+extern const char kLabelPattern_Help[];
+
+// A label pattern is a simple pattern that matches labels. It is used for
+// specifying visibility and other times when multiple targets need to be
+// referenced.
+class LabelPattern {
+ public:
+  enum Type {
+    MATCH = 1,           // Exact match for a given target.
+    DIRECTORY,           // Only targets in the file in the given directory.
+    RECURSIVE_DIRECTORY  // The given directory and any subdir.
+                         // (also indicates "public" when dir is empty).
+  };
+
+  LabelPattern();
+  LabelPattern(Type type,
+               const SourceDir& dir,
+               const std::string_view& name,
+               const Label& toolchain_label);
+  LabelPattern(const LabelPattern& other);
+  ~LabelPattern();
+
+  // Converts the given input string to a pattern. This does special stuff
+  // to treat the pattern as a label. Sets the error on failure.
+  static LabelPattern GetPattern(const SourceDir& current_dir,
+                                 const std::string_view& source_root,
+                                 const Value& value,
+                                 Err* err);
+
+  // Returns true if the given input string might match more than one thing.
+  static bool HasWildcard(const std::string& str);
+
+  // Returns true if this pattern matches the given label.
+  bool Matches(const Label& label) const;
+
+  // Returns true if any of the patterns in the vector match the label.
+  static bool VectorMatches(const std::vector<LabelPattern>& patterns,
+                            const Label& label);
+
+  // Returns a string representation of this pattern.
+  std::string Describe() const;
+
+  Type type() const { return type_; }
+
+  const SourceDir& dir() const { return dir_; }
+  const std::string& name() const { return name_; }
+
+  const Label& toolchain() const { return toolchain_; }
+  void set_toolchain(const Label& tc) { toolchain_ = tc; }
+
+ private:
+  // If nonempty, specifies the toolchain to use. If empty, this will match
+  // all toolchains. This is independent of the match type.
+  Label toolchain_;
+
+  Type type_;
+
+  // Used when type_ == PRIVATE and PRIVATE_RECURSIVE. This specifies the
+  // directory that to which the pattern is private to.
+  SourceDir dir_;
+
+  // Empty name means match everything. Otherwise the name must match
+  // exactly.
+  std::string name_;
+};
+
+#endif  // TOOLS_GN_LABEL_PATTERN_H_
diff --git a/src/gn/label_pattern_unittest.cc b/src/gn/label_pattern_unittest.cc
new file mode 100644 (file)
index 0000000..ad98401
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <iterator>
+
+#include "base/macros.h"
+#include "gn/err.h"
+#include "gn/label_pattern.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+namespace {
+
+struct PatternCase {
+  const char* input;
+  bool success;
+
+  LabelPattern::Type type;
+  const char* dir;
+  const char* name;
+  const char* toolchain;
+};
+
+}  // namespace
+
+TEST(LabelPattern, PatternParse) {
+  SourceDir current_dir("//foo/");
+  PatternCase cases[] = {
+    // Missing stuff.
+    {"", false, LabelPattern::MATCH, "", "", ""},
+    {":", false, LabelPattern::MATCH, "", "", ""},
+    // Normal things.
+    {":bar", true, LabelPattern::MATCH, "//foo/", "bar", ""},
+    {"//la:bar", true, LabelPattern::MATCH, "//la/", "bar", ""},
+    {"*", true, LabelPattern::RECURSIVE_DIRECTORY, "", "", ""},
+    {":*", true, LabelPattern::DIRECTORY, "//foo/", "", ""},
+    {"la:*", true, LabelPattern::DIRECTORY, "//foo/la/", "", ""},
+    {"la/*:*", true, LabelPattern::RECURSIVE_DIRECTORY, "//foo/la/", "", ""},
+    {"//la:*", true, LabelPattern::DIRECTORY, "//la/", "", ""},
+    {"./*", true, LabelPattern::RECURSIVE_DIRECTORY, "//foo/", "", ""},
+    {"foo/*", true, LabelPattern::RECURSIVE_DIRECTORY, "//foo/foo/", "", ""},
+    {"//l/*", true, LabelPattern::RECURSIVE_DIRECTORY, "//l/", "", ""},
+    // Toolchains.
+    {"//foo()", true, LabelPattern::MATCH, "//foo/", "foo", ""},
+    {"//foo(//bar)", true, LabelPattern::MATCH, "//foo/", "foo", "//bar:bar"},
+    {"//foo:*(//bar)", true, LabelPattern::DIRECTORY, "//foo/", "",
+     "//bar:bar"},
+    {"//foo/*(//bar)", true, LabelPattern::RECURSIVE_DIRECTORY, "//foo/", "",
+     "//bar:bar"},
+    // Wildcards in invalid places.
+    {"*foo*:bar", false, LabelPattern::MATCH, "", "", ""},
+    {"foo*:*bar", false, LabelPattern::MATCH, "", "", ""},
+    {"*foo:bar", false, LabelPattern::MATCH, "", "", ""},
+    {"foo:bar*", false, LabelPattern::MATCH, "", "", ""},
+    {"*:*", true, LabelPattern::RECURSIVE_DIRECTORY, "", "", ""},
+    // Invalid toolchain stuff.
+    {"//foo(//foo/bar:*)", false, LabelPattern::MATCH, "", "", ""},
+    {"//foo/*(*)", false, LabelPattern::MATCH, "", "", ""},
+    {"//foo(//bar", false, LabelPattern::MATCH, "", "", ""},
+    // Absolute paths.
+    {"/la/*", true, LabelPattern::RECURSIVE_DIRECTORY, "/la/", "", ""},
+    {"/la:bar", true, LabelPattern::MATCH, "/la/", "bar", ""},
+#if defined(OS_WIN)
+    {"/C:/la/*", true, LabelPattern::RECURSIVE_DIRECTORY, "/C:/la/", "", ""},
+    {"C:/la/*", true, LabelPattern::RECURSIVE_DIRECTORY, "/C:/la/", "", ""},
+    {"/C:/la:bar", true, LabelPattern::MATCH, "/C:/la/", "bar", ""},
+    {"C:/la:bar", true, LabelPattern::MATCH, "/C:/la/", "bar", ""},
+    {"C:foo", true, LabelPattern::MATCH, "//foo/C/", "foo", ""},
+#endif
+  };
+
+  for (size_t i = 0; i < std::size(cases); i++) {
+    const PatternCase& cur = cases[i];
+    Err err;
+    LabelPattern result = LabelPattern::GetPattern(
+        current_dir, std::string_view(), Value(nullptr, cur.input), &err);
+
+    EXPECT_EQ(cur.success, !err.has_error()) << i << " " << cur.input;
+    EXPECT_EQ(cur.type, result.type()) << i << " " << cur.input;
+    EXPECT_EQ(cur.dir, result.dir().value()) << i << " " << cur.input;
+    EXPECT_EQ(cur.name, result.name()) << i << " " << cur.input;
+    EXPECT_EQ(cur.toolchain, result.toolchain().GetUserVisibleName(false))
+        << i << " " << cur.input;
+  }
+}
+
+// Tests a non-empty source root which allows patterns to reference above the
+// source root.
+TEST(LabelPattern, PatternParseAboveSourceRoot) {
+  SourceDir current_dir("//foo/");
+  std::string source_root = "/foo/bar/baz/";
+
+  Err err;
+  LabelPattern result = LabelPattern::GetPattern(
+      current_dir, source_root, Value(nullptr, "../../../*"), &err);
+  ASSERT_FALSE(err.has_error());
+
+  EXPECT_EQ(LabelPattern::RECURSIVE_DIRECTORY, result.type());
+  EXPECT_EQ("/foo/", result.dir().value()) << result.dir().value();
+}
diff --git a/src/gn/label_ptr.h b/src/gn/label_ptr.h
new file mode 100644 (file)
index 0000000..cf30772
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LABEL_PTR_H_
+#define TOOLS_GN_LABEL_PTR_H_
+
+#include <stddef.h>
+
+#include <functional>
+
+#include "gn/label.h"
+
+class Config;
+class ParseNode;
+class Target;
+
+// Structure that holds a labeled "thing". This is used for various places
+// where we need to store lists of targets or configs. We sometimes populate
+// the pointers on another thread from where we compute the labels, so this
+// structure lets us save them separately. This also allows us to store the
+// location of the thing that added this dependency.
+template <typename T>
+struct LabelPtrPair {
+  using DestType = T;
+
+  LabelPtrPair() = default;
+
+  explicit LabelPtrPair(const Label& l) : label(l) {}
+
+  // This constructor is typically used in unit tests, it extracts the label
+  // automatically from a given pointer.
+  explicit LabelPtrPair(const T* p) : label(p->label()), ptr(p) {}
+
+  ~LabelPtrPair() = default;
+
+  Label label;
+  const T* ptr = nullptr;
+
+  // The origin of this dependency. This will be null for internally generated
+  // dependencies. This happens when a group is automatically expanded and that
+  // group's members are added to the target that depends on that group.
+  const ParseNode* origin = nullptr;
+};
+
+using LabelConfigPair = LabelPtrPair<Config>;
+using LabelTargetPair = LabelPtrPair<Target>;
+
+using LabelConfigVector = std::vector<LabelConfigPair>;
+using LabelTargetVector = std::vector<LabelTargetPair>;
+
+// Default comparison operators -----------------------------------------------
+//
+// The default hash and comparison operators operate on the label, which should
+// always be valid, whereas the pointer is sometimes null.
+
+template <typename T>
+inline bool operator==(const LabelPtrPair<T>& a, const LabelPtrPair<T>& b) {
+  return a.label == b.label;
+}
+
+template <typename T>
+inline bool operator<(const LabelPtrPair<T>& a, const LabelPtrPair<T>& b) {
+  return a.label < b.label;
+}
+
+namespace std {
+
+template <typename T>
+struct hash<LabelPtrPair<T>> {
+  std::size_t operator()(const LabelPtrPair<T>& v) const {
+    hash<Label> h;
+    return h(v.label);
+  }
+};
+
+}  // namespace std
+
+#endif  // TOOLS_GN_LABEL_PTR_H_
diff --git a/src/gn/label_unittest.cc b/src/gn/label_unittest.cc
new file mode 100644 (file)
index 0000000..159bba8
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <iterator>
+
+#include "base/macros.h"
+#include "gn/err.h"
+#include "gn/label.h"
+#include "gn/value.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+struct ParseDepStringCase {
+  const char* cur_dir;
+  const char* str;
+  bool success;
+  const char* expected_dir;
+  const char* expected_name;
+  const char* expected_toolchain_dir;
+  const char* expected_toolchain_name;
+};
+
+}  // namespace
+
+TEST(Label, Resolve) {
+  ParseDepStringCase cases[] = {
+    {"//chrome/", "", false, "", "", "", ""},
+    {"//chrome/", "/", false, "", "", "", ""},
+    {"//chrome/", ":", false, "", "", "", ""},
+    {"//chrome/", "/:", false, "", "", "", ""},
+    {"//chrome/", "blah", true, "//chrome/blah/", "blah", "//t/", "d"},
+    {"//chrome/", "blah:bar", true, "//chrome/blah/", "bar", "//t/", "d"},
+    // Absolute paths.
+    {"//chrome/", "/chrome:bar", true, "/chrome/", "bar", "//t/", "d"},
+    {"//chrome/", "/chrome/:bar", true, "/chrome/", "bar", "//t/", "d"},
+#if defined(OS_WIN)
+    {"//chrome/", "/C:/chrome:bar", true, "/C:/chrome/", "bar", "//t/", "d"},
+    {"//chrome/", "/C:/chrome/:bar", true, "/C:/chrome/", "bar", "//t/", "d"},
+    {"//chrome/", "C:/chrome:bar", true, "/C:/chrome/", "bar", "//t/", "d"},
+#endif
+    // Refers to root dir.
+    {"//chrome/", "//:bar", true, "//", "bar", "//t/", "d"},
+    // Implicit directory
+    {"//chrome/", ":bar", true, "//chrome/", "bar", "//t/", "d"},
+    {"//chrome/renderer/", ":bar", true, "//chrome/renderer/", "bar", "//t/",
+     "d"},
+    // Implicit names.
+    {"//chrome/", "//base", true, "//base/", "base", "//t/", "d"},
+    {"//chrome/", "//base/i18n", true, "//base/i18n/", "i18n", "//t/", "d"},
+    {"//chrome/", "//base/i18n:foo", true, "//base/i18n/", "foo", "//t/", "d"},
+    {"//chrome/", "//", false, "", "", "", ""},
+    // Toolchain parsing.
+    {"//chrome/", "//chrome:bar(//t:n)", true, "//chrome/", "bar", "//t/", "n"},
+    {"//chrome/", "//chrome:bar(//t)", true, "//chrome/", "bar", "//t/", "t"},
+    {"//chrome/", "//chrome:bar(//t:)", true, "//chrome/", "bar", "//t/", "t"},
+    {"//chrome/", "//chrome:bar()", true, "//chrome/", "bar", "//t/", "d"},
+    {"//chrome/", "//chrome:bar(foo)", true, "//chrome/", "bar",
+     "//chrome/foo/", "foo"},
+    {"//chrome/", "//chrome:bar(:foo)", true, "//chrome/", "bar", "//chrome/",
+     "foo"},
+    // TODO(brettw) it might be nice to make this an error:
+    //{"//chrome/", "//chrome:bar())", false, "", "", "", "" },
+    {"//chrome/", "//chrome:bar(//t:bar(tc))", false, "", "", "", ""},
+    {"//chrome/", "//chrome:bar(()", false, "", "", "", ""},
+    {"//chrome/", "(t:b)", false, "", "", "", ""},
+    {"//chrome/", ":bar(//t/b)", true, "//chrome/", "bar", "//t/b/", "b"},
+    {"//chrome/", ":bar(/t/b)", true, "//chrome/", "bar", "/t/b/", "b"},
+    {"//chrome/", ":bar(t/b)", true, "//chrome/", "bar", "//chrome/t/b/", "b"},
+  };
+
+  Label default_toolchain(SourceDir("//t/"), "d");
+
+  for (size_t i = 0; i < std::size(cases); i++) {
+    const ParseDepStringCase& cur = cases[i];
+
+    std::string location, name;
+    Err err;
+    Value v(nullptr, Value::STRING);
+    v.string_value() = cur.str;
+    Label result = Label::Resolve(SourceDir(cur.cur_dir), std::string_view(),
+                                  default_toolchain, v, &err);
+    EXPECT_EQ(cur.success, !err.has_error()) << i << " " << cur.str;
+    if (!err.has_error() && cur.success) {
+      EXPECT_EQ(cur.expected_dir, result.dir().value()) << i << " " << cur.str;
+      EXPECT_EQ(cur.expected_name, result.name()) << i << " " << cur.str;
+      EXPECT_EQ(cur.expected_toolchain_dir, result.toolchain_dir().value())
+          << i << " " << cur.str;
+      EXPECT_EQ(cur.expected_toolchain_name, result.toolchain_name())
+          << i << " " << cur.str;
+    }
+  }
+}
+
+// Tests the case where the path resolves to something above "//". It should get
+// converted to an absolute path "/foo/bar".
+TEST(Label, ResolveAboveRootBuildDir) {
+  Label default_toolchain(SourceDir("//t/"), "d");
+
+  std::string location, name;
+  Err err;
+
+  SourceDir cur_dir("//cur/");
+  std::string source_root("/foo/bar/baz");
+
+  // No source root given, should not go above the root build dir.
+  Label result = Label::Resolve(cur_dir, std::string_view(), default_toolchain,
+                                Value(nullptr, "../../..:target"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("//", result.dir().value()) << result.dir().value();
+  EXPECT_EQ("target", result.name());
+
+  // Source root provided, it should go into that.
+  result = Label::Resolve(cur_dir, source_root, default_toolchain,
+                          Value(nullptr, "../../..:target"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("/foo/", result.dir().value()) << result.dir().value();
+  EXPECT_EQ("target", result.name());
+
+  // It shouldn't go up higher than the system root.
+  result = Label::Resolve(cur_dir, source_root, default_toolchain,
+                          Value(nullptr, "../../../../..:target"), &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("/", result.dir().value()) << result.dir().value();
+  EXPECT_EQ("target", result.name());
+
+  // Test an absolute label that goes above the source root. This currently
+  // stops at the source root. It should arguably keep going and produce "/foo/"
+  // but this test just makes sure the current behavior isn't regressed by
+  // accident.
+  result = Label::Resolve(cur_dir, source_root, default_toolchain,
+                          Value(nullptr, "//../.."), &err);
+  EXPECT_FALSE(err.has_error()) << err.message();
+  EXPECT_EQ("/foo/", result.dir().value()) << result.dir().value();
+  EXPECT_EQ("foo", result.name());
+}
diff --git a/src/gn/lib_file.cc b/src/gn/lib_file.cc
new file mode 100644 (file)
index 0000000..cbb67f1
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/lib_file.h"
+
+#include "base/logging.h"
+
+LibFile::LibFile(const SourceFile& source_file) : source_file_(source_file) {}
+
+LibFile::LibFile(const std::string_view& lib_name)
+    : name_(lib_name.data(), lib_name.size()) {
+  DCHECK(!lib_name.empty());
+}
+
+const std::string& LibFile::value() const {
+  return is_source_file() ? source_file_.value() : name_;
+}
+
+const SourceFile& LibFile::source_file() const {
+  DCHECK(is_source_file());
+  return source_file_;
+}
diff --git a/src/gn/lib_file.h b/src/gn/lib_file.h
new file mode 100644 (file)
index 0000000..47952f8
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LIB_FILE_H_
+#define TOOLS_GN_LIB_FILE_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+#include "gn/source_file.h"
+
+// Represents an entry in "libs" list. Can be either a path (a SourceFile) or
+// a library name (a string).
+class LibFile {
+ public:
+  LibFile() = default;
+
+  explicit LibFile(const std::string_view& lib_name);
+  explicit LibFile(const SourceFile& source_file);
+
+  bool is_source_file() const { return name_.empty(); }
+
+  // Returns name, or source_file().value() (whichever is set).
+  const std::string& value() const;
+  const SourceFile& source_file() const;
+
+  bool operator==(const LibFile& other) const {
+    return value() == other.value();
+  }
+  bool operator!=(const LibFile& other) const { return !operator==(other); }
+  bool operator<(const LibFile& other) const { return value() < other.value(); }
+
+ private:
+  std::string name_;
+  SourceFile source_file_;
+};
+
+namespace std {
+
+template <>
+struct hash<LibFile> {
+  std::size_t operator()(const LibFile& v) const {
+    hash<std::string> h;
+    return h(v.value());
+  }
+};
+
+}  // namespace std
+
+#endif  // TOOLS_GN_LIB_FILE_H_
diff --git a/src/gn/loader.cc b/src/gn/loader.cc
new file mode 100644 (file)
index 0000000..c615170
--- /dev/null
@@ -0,0 +1,448 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/loader.h"
+
+#include <memory>
+
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file_manager.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/scope_per_file_provider.h"
+#include "gn/settings.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/trace.h"
+
+namespace {
+
+struct SourceFileAndOrigin {
+  SourceFileAndOrigin(const SourceFile& f, const LocationRange& o)
+      : file(f), origin(o) {}
+
+  SourceFile file;
+  LocationRange origin;
+};
+
+}  // namespace
+
+// Identifies one time a file is loaded in a given toolchain so we don't load
+// it more than once.
+struct LoaderImpl::LoadID {
+  LoadID() = default;
+  LoadID(const SourceFile& f, const Label& tc_name)
+      : file(f), toolchain_name(tc_name) {}
+
+  bool operator<(const LoadID& other) const {
+    if (file.value() == other.file.value())
+      return toolchain_name < other.toolchain_name;
+    return file < other.file;
+  }
+
+  SourceFile file;
+  Label toolchain_name;
+};
+
+// Our tracking information for a toolchain.
+struct LoaderImpl::ToolchainRecord {
+  // The default toolchain label can be empty for the first time the default
+  // toolchain is loaded, since we don't know it yet. This will be fixed up
+  // later. It should be valid in all other cases.
+  ToolchainRecord(const BuildSettings* build_settings,
+                  const Label& toolchain_label,
+                  const Label& default_toolchain_label)
+      : settings(
+            build_settings,
+            GetOutputSubdirName(toolchain_label,
+                                toolchain_label == default_toolchain_label)),
+        is_toolchain_loaded(false),
+        is_config_loaded(false) {
+    settings.set_default_toolchain_label(default_toolchain_label);
+    settings.set_toolchain_label(toolchain_label);
+  }
+
+  Settings settings;
+
+  bool is_toolchain_loaded;
+  bool is_config_loaded;
+
+  std::vector<SourceFileAndOrigin> waiting_on_me;
+};
+
+// -----------------------------------------------------------------------------
+
+const void* const Loader::kDefaultToolchainKey = &kDefaultToolchainKey;
+
+Loader::Loader() = default;
+
+Loader::~Loader() = default;
+
+void Loader::Load(const Label& label, const LocationRange& origin) {
+  Load(BuildFileForLabel(label), origin, label.GetToolchainLabel());
+}
+
+// -----------------------------------------------------------------------------
+
+LoaderImpl::LoaderImpl(const BuildSettings* build_settings)
+    : pending_loads_(0), build_settings_(build_settings) {
+  // There may not be an active TaskRunner at this point. When that's the case,
+  // the calling code is expected to call set_task_runner().
+  task_runner_ = MsgLoop::Current();
+}
+
+LoaderImpl::~LoaderImpl() = default;
+
+void LoaderImpl::Load(const SourceFile& file,
+                      const LocationRange& origin,
+                      const Label& in_toolchain_name) {
+  const Label& toolchain_name = in_toolchain_name.is_null()
+                                    ? default_toolchain_label_
+                                    : in_toolchain_name;
+  LoadID load_id(file, toolchain_name);
+  if (!invocations_.insert(load_id).second)
+    return;  // Already in set, so this file was already loaded or schedulerd.
+
+  if (toolchain_records_.empty()) {
+    // Nothing loaded, need to load the default build config. The initial load
+    // should not specify a toolchain.
+    DCHECK(toolchain_name.is_null());
+
+    std::unique_ptr<ToolchainRecord> new_record =
+        std::make_unique<ToolchainRecord>(build_settings_, Label(), Label());
+    ToolchainRecord* record = new_record.get();
+    Label empty_label;  // VS issues spurious warning using ...[Label()].
+    toolchain_records_[empty_label] = std::move(new_record);
+
+    // The default build config is no dependent on the toolchain definition,
+    // since we need to load the build config before we know what the default
+    // toolchain name is.
+    record->is_toolchain_loaded = true;
+
+    record->waiting_on_me.push_back(SourceFileAndOrigin(file, origin));
+    ScheduleLoadBuildConfig(&record->settings, Scope::KeyValueMap());
+
+    return;
+  }
+
+  ToolchainRecord* record;
+  if (toolchain_name.is_null())
+    record = toolchain_records_[default_toolchain_label_].get();
+  else
+    record = toolchain_records_[toolchain_name].get();
+
+  if (!record) {
+    DCHECK(!default_toolchain_label_.is_null());
+
+    // No reference to this toolchain found yet, make one.
+    std::unique_ptr<ToolchainRecord> new_record =
+        std::make_unique<ToolchainRecord>(build_settings_, toolchain_name,
+                                          default_toolchain_label_);
+    record = new_record.get();
+    toolchain_records_[toolchain_name] = std::move(new_record);
+
+    // Schedule a load of the toolchain using the default one.
+    Load(BuildFileForLabel(toolchain_name), origin, default_toolchain_label_);
+  }
+
+  if (record->is_config_loaded)
+    ScheduleLoadFile(&record->settings, origin, file);
+  else
+    record->waiting_on_me.push_back(SourceFileAndOrigin(file, origin));
+}
+
+void LoaderImpl::ToolchainLoaded(const Toolchain* toolchain) {
+  ToolchainRecord* record = toolchain_records_[toolchain->label()].get();
+  if (!record) {
+    DCHECK(!default_toolchain_label_.is_null());
+    std::unique_ptr<ToolchainRecord> new_record =
+        std::make_unique<ToolchainRecord>(build_settings_, toolchain->label(),
+                                          default_toolchain_label_);
+    record = new_record.get();
+    toolchain_records_[toolchain->label()] = std::move(new_record);
+  }
+  record->is_toolchain_loaded = true;
+
+  // The default build config is loaded first, then its toolchain. Secondary
+  // ones are loaded in the opposite order so we can pass toolchain parameters
+  // to the build config. So we may or may not have a config at this point.
+  if (!record->is_config_loaded) {
+    ScheduleLoadBuildConfig(&record->settings, toolchain->args());
+  } else {
+    // There should be nobody waiting on this if the build config is already
+    // loaded.
+    DCHECK(record->waiting_on_me.empty());
+  }
+}
+
+Label LoaderImpl::GetDefaultToolchain() const {
+  return default_toolchain_label_;
+}
+
+const Settings* LoaderImpl::GetToolchainSettings(const Label& label) const {
+  ToolchainRecordMap::const_iterator found_toolchain;
+  if (label.is_null()) {
+    if (default_toolchain_label_.is_null())
+      return nullptr;
+    found_toolchain = toolchain_records_.find(default_toolchain_label_);
+  } else {
+    found_toolchain = toolchain_records_.find(label);
+  }
+
+  if (found_toolchain == toolchain_records_.end())
+    return nullptr;
+  return &found_toolchain->second->settings;
+}
+
+SourceFile LoaderImpl::BuildFileForLabel(const Label& label) const {
+  return SourceFile(
+      label.dir().value() + "BUILD" + build_file_extension_ + ".gn");
+}
+
+void LoaderImpl::ScheduleLoadFile(const Settings* settings,
+                                  const LocationRange& origin,
+                                  const SourceFile& file) {
+  Err err;
+  pending_loads_++;
+  if (!AsyncLoadFile(
+          origin, settings->build_settings(), file,
+          [this, settings, file, origin](const ParseNode* parse_node) {
+            BackgroundLoadFile(settings, file, origin, parse_node);
+          },
+          &err)) {
+    g_scheduler->FailWithError(err);
+    DecrementPendingLoads();
+  }
+}
+
+void LoaderImpl::ScheduleLoadBuildConfig(
+    Settings* settings,
+    const Scope::KeyValueMap& toolchain_overrides) {
+  Err err;
+  pending_loads_++;
+  if (!AsyncLoadFile(
+          LocationRange(), settings->build_settings(),
+          settings->build_settings()->build_config_file(),
+          [this, settings, toolchain_overrides](const ParseNode* root) {
+            BackgroundLoadBuildConfig(settings, toolchain_overrides, root);
+          },
+          &err)) {
+    g_scheduler->FailWithError(err);
+    DecrementPendingLoads();
+  }
+}
+
+void LoaderImpl::BackgroundLoadFile(const Settings* settings,
+                                    const SourceFile& file_name,
+                                    const LocationRange& origin,
+                                    const ParseNode* root) {
+  if (!root) {
+    task_runner_->PostTask([this]() { DecrementPendingLoads(); });
+    return;
+  }
+
+  if (g_scheduler->verbose_logging()) {
+    g_scheduler->Log("Running",
+                     file_name.value() + " with toolchain " +
+                         settings->toolchain_label().GetUserVisibleName(false));
+  }
+
+  Scope our_scope(settings->base_config());
+  ScopePerFileProvider per_file_provider(&our_scope, true);
+  our_scope.set_source_dir(file_name.GetDir());
+  our_scope.AddBuildDependencyFile(file_name);
+
+  // Targets, etc. generated as part of running this file will end up here.
+  Scope::ItemVector collected_items;
+  our_scope.set_item_collector(&collected_items);
+
+  ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE, file_name.value());
+  trace.SetToolchain(settings->toolchain_label());
+
+  Err err;
+  root->Execute(&our_scope, &err);
+  if (!err.has_error())
+    our_scope.CheckForUnusedVars(&err);
+
+  if (err.has_error()) {
+    if (!origin.is_null())
+      err.AppendSubErr(Err(origin, "which caused the file to be included."));
+
+    if (!settings->is_default())
+      err.set_toolchain_label(settings->toolchain_label());
+
+    g_scheduler->FailWithError(err);
+  }
+
+  // Pass all of the items that were defined off to the builder.
+  for (auto& item : collected_items)
+    settings->build_settings()->ItemDefined(std::move(item));
+
+  trace.Done();
+
+  task_runner_->PostTask([this]() { DidLoadFile(); });
+}
+
+void LoaderImpl::BackgroundLoadBuildConfig(
+    Settings* settings,
+    const Scope::KeyValueMap& toolchain_overrides,
+    const ParseNode* root) {
+  if (!root) {
+    task_runner_->PostTask([this]() { DecrementPendingLoads(); });
+    return;
+  }
+
+  Scope* base_config = settings->base_config();
+  base_config->set_source_dir(SourceDir("//"));
+  base_config->AddBuildDependencyFile(
+      settings->build_settings()->build_config_file());
+
+  settings->build_settings()->build_args().SetupRootScope(base_config,
+                                                          toolchain_overrides);
+
+  base_config->SetProcessingBuildConfig();
+
+  // See kDefaultToolchainKey in the header.
+  Label default_toolchain_label;
+  if (settings->is_default())
+    base_config->SetProperty(kDefaultToolchainKey, &default_toolchain_label);
+
+  ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE,
+                    settings->build_settings()->build_config_file().value());
+  trace.SetToolchain(settings->toolchain_label());
+
+  // Run the BUILDCONFIG with its directory as the current one. We want
+  // BUILDCONFIG to modify the base_config so can't make a copy or a nested one.
+  base_config->set_source_dir(
+      settings->build_settings()->build_config_file().GetDir());
+
+  Err err;
+  root->Execute(base_config, &err);
+
+  // Put back the root as the default source dir. This probably isn't necessary
+  // as other scopes will set their directories to their own path, but it's a
+  // better default than the build config's directory.
+  base_config->set_source_dir(SourceDir("//"));
+
+  // Clear all private variables left in the scope. We want the root build
+  // config to be like a .gni file in that variables beginning with an
+  // underscore aren't exported.
+  base_config->RemovePrivateIdentifiers();
+
+  trace.Done();
+
+  if (err.has_error()) {
+    if (!settings->is_default())
+      err.set_toolchain_label(settings->toolchain_label());
+
+    g_scheduler->FailWithError(err);
+  }
+
+  base_config->ClearProcessingBuildConfig();
+  if (settings->is_default()) {
+    // The default toolchain must have been set in the default build config
+    // file.
+    if (default_toolchain_label.is_null()) {
+      g_scheduler->FailWithError(Err(
+          Location(),
+          "The default build config file did not call set_default_toolchain()",
+          "If you don't call this, I can't figure out what toolchain to use\n"
+          "for all of this code."));
+    } else {
+      DCHECK(settings->toolchain_label().is_null());
+      settings->set_toolchain_label(default_toolchain_label);
+    }
+  }
+
+  task_runner_->PostTask(
+      [this, toolchain_label = settings->toolchain_label()]() {
+        DidLoadBuildConfig(toolchain_label);
+      });
+}
+
+void LoaderImpl::DidLoadFile() {
+  DecrementPendingLoads();
+}
+
+void LoaderImpl::DidLoadBuildConfig(const Label& label) {
+  // Do not return early, we must call DecrementPendingLoads() at the bottom.
+
+  ToolchainRecordMap::iterator found_toolchain = toolchain_records_.find(label);
+  ToolchainRecord* record = nullptr;
+  if (found_toolchain == toolchain_records_.end()) {
+    // When loading the default build config, we'll insert it into the record
+    // map with an empty label since we don't yet know what to call it.
+    //
+    // In this case, we should have exactly one entry in the map with an empty
+    // label. We now need to fix up the naming so it refers to the "real" one.
+    CHECK_EQ(1U, toolchain_records_.size());
+    ToolchainRecordMap::iterator empty_label = toolchain_records_.find(Label());
+    CHECK(empty_label != toolchain_records_.end());
+
+    // Fix up the toolchain record.
+    std::unique_ptr<ToolchainRecord> moved_record =
+        std::move(empty_label->second);
+    record = moved_record.get();
+    toolchain_records_[label] = std::move(moved_record);
+    toolchain_records_.erase(empty_label);
+
+    // Save the default toolchain label.
+    default_toolchain_label_ = label;
+    DCHECK(record->settings.default_toolchain_label().is_null());
+    record->settings.set_default_toolchain_label(label);
+
+    // The settings object should have the toolchain label already set.
+    DCHECK(!record->settings.toolchain_label().is_null());
+
+    // Update any stored invocations that refer to the empty toolchain label.
+    // This will normally only be one, for the root build file, so brute-force
+    // is OK.
+    LoadIDSet old_loads;
+    invocations_.swap(old_loads);
+    for (const auto& load : old_loads) {
+      if (load.toolchain_name.is_null()) {
+        // Fix up toolchain label
+        invocations_.emplace(load.file, label);
+      } else {
+        // Can keep the old one.
+        invocations_.insert(load);
+      }
+    }
+  } else {
+    record = found_toolchain->second.get();
+  }
+
+  DCHECK(!record->is_config_loaded);
+  DCHECK(record->is_toolchain_loaded);
+  record->is_config_loaded = true;
+
+  // Schedule all waiting file loads.
+  for (const auto& waiting : record->waiting_on_me)
+    ScheduleLoadFile(&record->settings, waiting.origin, waiting.file);
+  record->waiting_on_me.clear();
+
+  DecrementPendingLoads();
+}
+
+void LoaderImpl::DecrementPendingLoads() {
+  DCHECK_GT(pending_loads_, 0);
+  pending_loads_--;
+  if (pending_loads_ == 0 && complete_callback_)
+    complete_callback_();
+}
+
+bool LoaderImpl::AsyncLoadFile(const LocationRange& origin,
+                               const BuildSettings* build_settings,
+                               const SourceFile& file_name,
+                               std::function<void(const ParseNode*)> callback,
+                               Err* err) {
+  if (async_load_file_) {
+    return async_load_file_(origin, build_settings, file_name,
+                            std::move(callback), err);
+  }
+  return g_scheduler->input_file_manager()->AsyncLoadFile(
+      origin, build_settings, file_name, std::move(callback), err);
+}
diff --git a/src/gn/loader.h b/src/gn/loader.h
new file mode 100644 (file)
index 0000000..8f95bb8
--- /dev/null
@@ -0,0 +1,187 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LOADER_H_
+#define TOOLS_GN_LOADER_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/memory/ref_counted.h"
+#include "gn/label.h"
+#include "gn/scope.h"
+#include "util/msg_loop.h"
+
+class BuildSettings;
+class LocationRange;
+class Settings;
+class SourceFile;
+class Toolchain;
+
+// The loader manages execution of the different build files. It receives
+// requests (normally from the Builder) when new references are found, and also
+// manages loading the build config files.
+//
+// This loader class is abstract so it can be mocked out for testing the
+// Builder.
+class Loader : public base::RefCountedThreadSafe<Loader> {
+ public:
+  Loader();
+
+  // Loads the given file in the conext of the given toolchain. The initial
+  // call to this (the one that actually starts the generation) should have an
+  // empty toolchain name, which will trigger the load of the default build
+  // config.
+  virtual void Load(const SourceFile& file,
+                    const LocationRange& origin,
+                    const Label& toolchain_name) = 0;
+
+  // Notification that the given toolchain has loaded. This will unblock files
+  // waiting on this definition.
+  virtual void ToolchainLoaded(const Toolchain* toolchain) = 0;
+
+  // Returns the label of the default toolchain.
+  virtual Label GetDefaultToolchain() const = 0;
+
+  // Returns information about the toolchain with the given label. Will return
+  // false if we haven't processed this toolchain yet.
+  virtual const Settings* GetToolchainSettings(const Label& label) const = 0;
+
+  // Returns the build file that the given label references.
+  virtual SourceFile BuildFileForLabel(const Label& label) const = 0;
+
+  // Helper function that extracts the file and toolchain name from the given
+  // label, and calls Load().
+  void Load(const Label& label, const LocationRange& origin);
+
+  // When processing the default build config, we want to capture the argument
+  // of set_default_build_config. The implementation of that function uses this
+  // constant as a property key to get the Label* out of the scope where the
+  // label should be stored.
+  static const void* const kDefaultToolchainKey;
+
+ protected:
+  friend class base::RefCountedThreadSafe<Loader>;
+  virtual ~Loader();
+};
+
+class LoaderImpl : public Loader {
+ public:
+  // Callback to emulate InputFileManager::AsyncLoadFile.
+  using AsyncLoadFileCallback =
+      std::function<bool(const LocationRange&,
+                         const BuildSettings*,
+                         const SourceFile&,
+                         std::function<void(const ParseNode*)>,
+                         Err*)>;
+
+  explicit LoaderImpl(const BuildSettings* build_settings);
+
+  // Loader implementation.
+  void Load(const SourceFile& file,
+            const LocationRange& origin,
+            const Label& toolchain_name) override;
+  void ToolchainLoaded(const Toolchain* toolchain) override;
+  Label GetDefaultToolchain() const override;
+  const Settings* GetToolchainSettings(const Label& label) const override;
+  SourceFile BuildFileForLabel(const Label& label) const override;
+
+  // Sets the task runner corresponding to the main thread. By default this
+  // class will use the thread active during construction, but there is not
+  // a task runner active during construction all the time.
+  void set_task_runner(MsgLoop* task_runner) { task_runner_ = task_runner; }
+
+  // The complete callback is called whenever there are no more pending loads.
+  // Called on the main thread only. This may be called more than once if the
+  // queue is drained, but then more stuff gets added.
+  void set_complete_callback(std::function<void()> cb) {
+    complete_callback_ = std::move(cb);
+  }
+
+  // This callback is used when the loader finds it wants to load a file.
+  void set_async_load_file(AsyncLoadFileCallback cb) {
+    async_load_file_ = std::move(cb);
+  }
+
+  // Sets the additional extension for build files in this build.
+  // The resulting file name will be "BUILD.<extension>.gn".
+  void set_build_file_extension(std::string extension) {
+    build_file_extension_ = "." + extension;
+  }
+
+  const Label& default_toolchain_label() const {
+    return default_toolchain_label_;
+  }
+
+ private:
+  struct LoadID;
+  struct ToolchainRecord;
+
+  ~LoaderImpl() override;
+
+  // Schedules the input file manager to load the given file.
+  void ScheduleLoadFile(const Settings* settings,
+                        const LocationRange& origin,
+                        const SourceFile& file);
+  void ScheduleLoadBuildConfig(Settings* settings,
+                               const Scope::KeyValueMap& toolchain_overrides);
+
+  // Runs the given file on the background thread. These are called by the
+  // input file manager.
+  void BackgroundLoadFile(const Settings* settings,
+                          const SourceFile& file_name,
+                          const LocationRange& origin,
+                          const ParseNode* root);
+  void BackgroundLoadBuildConfig(Settings* settings,
+                                 const Scope::KeyValueMap& toolchain_overrides,
+                                 const ParseNode* root);
+
+  // Posted to the main thread when any file other than a build config file
+  // file has completed running.
+  void DidLoadFile();
+
+  // Posted to the main thread when any build config file has completed
+  // running. The label should be the name of the toolchain.
+  //
+  // If there is no defauled toolchain loaded yet, we'll assume that the first
+  // call to this indicates to the default toolchain, and this function will
+  // set the default toolchain name to the given label.
+  void DidLoadBuildConfig(const Label& label);
+
+  // Decrements the pending_loads_ variable and issues the complete callback if
+  // necessary.
+  void DecrementPendingLoads();
+
+  // Forwards to the appropriate location to load the file.
+  bool AsyncLoadFile(const LocationRange& origin,
+                     const BuildSettings* build_settings,
+                     const SourceFile& file_name,
+                     std::function<void(const ParseNode*)> callback,
+                     Err* err);
+
+  MsgLoop* task_runner_;
+
+  int pending_loads_;
+  std::function<void()> complete_callback_;
+
+  // When non-null, use this callback instead of the InputFileManager for
+  // mocking purposes.
+  AsyncLoadFileCallback async_load_file_;
+
+  using LoadIDSet = std::set<LoadID>;
+  LoadIDSet invocations_;
+
+  const BuildSettings* build_settings_;
+  Label default_toolchain_label_;
+
+  // Records for the build config file loads.
+  using ToolchainRecordMap = std::map<Label, std::unique_ptr<ToolchainRecord>>;
+  ToolchainRecordMap toolchain_records_;
+
+  std::string build_file_extension_;
+};
+
+#endif  // TOOLS_GN_LOADER_H_
diff --git a/src/gn/loader_unittest.cc b/src/gn/loader_unittest.cc
new file mode 100644 (file)
index 0000000..ce14894
--- /dev/null
@@ -0,0 +1,393 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/loader.h"
+#include "gn/parse_tree.h"
+#include "gn/parser.h"
+#include "gn/scheduler.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/tokenizer.h"
+#include "util/msg_loop.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool ItemContainsBuildDependencyFile(const Item* item,
+                                     const SourceFile& source_file) {
+  const auto& build_dependency_files = item->build_dependency_files();
+  return build_dependency_files.end() !=
+         build_dependency_files.find(source_file);
+}
+
+class MockBuilder {
+ public:
+  void OnItemDefined(std::unique_ptr<Item> item);
+  std::vector<const Item*> GetAllItems() const;
+
+ private:
+  std::vector<std::unique_ptr<Item>> items_;
+};
+
+void MockBuilder::OnItemDefined(std::unique_ptr<Item> item) {
+  items_.push_back(std::move(item));
+}
+
+std::vector<const Item*> MockBuilder::GetAllItems() const {
+  std::vector<const Item*> result;
+  for (const auto& item : items_) {
+    result.push_back(item.get());
+  }
+
+  return result;
+}
+
+class MockInputFileManager {
+ public:
+  using Callback = std::function<void(const ParseNode*)>;
+
+  MockInputFileManager();
+  ~MockInputFileManager();
+
+  LoaderImpl::AsyncLoadFileCallback GetAsyncCallback();
+
+  // Sets a given response for a given source file.
+  void AddCannedResponse(const SourceFile& source_file,
+                         const std::string& source);
+
+  // Returns true if there is/are pending load(s) matching the given file(s).
+  bool HasOnePending(const SourceFile& f) const;
+  bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const;
+
+  void IssueAllPending();
+
+ private:
+  struct CannedResult {
+    std::unique_ptr<InputFile> input_file;
+    std::vector<Token> tokens;
+    std::unique_ptr<ParseNode> root;
+  };
+
+  InputFileManager::SyncLoadFileCallback GetSyncCallback();
+
+  bool AsyncLoadFile(const LocationRange& origin,
+                     const BuildSettings* build_settings,
+                     const SourceFile& file_name,
+                     const Callback& callback,
+                     Err* err) {
+    pending_.push_back(std::make_pair(file_name, callback));
+    return true;
+  }
+
+  using CannedResponseMap = std::map<SourceFile, std::unique_ptr<CannedResult>>;
+  CannedResponseMap canned_responses_;
+
+  std::vector<std::pair<SourceFile, Callback>> pending_;
+};
+
+MockInputFileManager::MockInputFileManager() {
+  g_scheduler->input_file_manager()->set_load_file_callback(GetSyncCallback());
+}
+
+MockInputFileManager::~MockInputFileManager() {
+  g_scheduler->input_file_manager()->set_load_file_callback(nullptr);
+}
+
+LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetAsyncCallback() {
+  return
+      [this](const LocationRange& origin, const BuildSettings* build_settings,
+             const SourceFile& file_name, const Callback& callback, Err* err) {
+        return AsyncLoadFile(origin, build_settings, file_name, callback, err);
+      };
+}
+
+InputFileManager::SyncLoadFileCallback MockInputFileManager::GetSyncCallback() {
+  return
+      [this](const SourceFile& file_name, InputFile* file) {
+        CannedResponseMap::const_iterator found = canned_responses_.find(file_name);
+        if (found == canned_responses_.end())
+          return false;
+        file->SetContents(found->second->input_file->contents());
+        return true;
+      };
+}
+
+// Sets a given response for a given source file.
+void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
+                                             const std::string& source) {
+  std::unique_ptr<CannedResult> canned = std::make_unique<CannedResult>();
+  canned->input_file = std::make_unique<InputFile>(source_file);
+  canned->input_file->SetContents(source);
+
+  // Tokenize.
+  Err err;
+  canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Parse.
+  canned->root = Parser::Parse(canned->tokens, &err);
+  if (err.has_error())
+    err.PrintToStdout();
+  EXPECT_FALSE(err.has_error());
+
+  canned_responses_[source_file] = std::move(canned);
+}
+
+bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
+  return pending_.size() == 1u && pending_[0].first == f;
+}
+
+bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
+                                         const SourceFile& f2) const {
+  if (pending_.size() != 2u)
+    return false;
+  return pending_[0].first == f1 && pending_[1].first == f2;
+}
+
+void MockInputFileManager::IssueAllPending() {
+  BlockNode block(BlockNode::DISCARDS_RESULT);  // Default response.
+
+  for (const auto& cur : pending_) {
+    CannedResponseMap::const_iterator found = canned_responses_.find(cur.first);
+    if (found == canned_responses_.end())
+      cur.second(&block);
+    else
+      cur.second(found->second->root.get());
+  }
+  pending_.clear();
+}
+
+// LoaderTest ------------------------------------------------------------------
+
+class LoaderTest : public TestWithScheduler {
+ public:
+  LoaderTest() { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); }
+
+ protected:
+  BuildSettings build_settings_;
+  MockBuilder mock_builder_;
+  MockInputFileManager mock_ifm_;
+};
+
+}  // namespace
+
+// -----------------------------------------------------------------------------
+
+TEST_F(LoaderTest, Foo) {
+  SourceFile build_config("//build/config/BUILDCONFIG.gn");
+  build_settings_.set_build_config_file(build_config);
+
+  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
+
+  // The default toolchain needs to be set by the build config file.
+  mock_ifm_.AddCannedResponse(build_config,
+                              "set_default_toolchain(\"//tc:tc\")");
+
+  loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
+
+  // Request the root build file be loaded. This should kick off the default
+  // build config loading.
+  SourceFile root_build("//BUILD.gn");
+  loader->Load(root_build, LocationRange(), Label());
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Completing the build config load should kick off the root build file load.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
+
+  // Load the root build file.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // Schedule some other file to load in another toolchain.
+  Label second_tc(SourceDir("//tc2/"), "tc2");
+  SourceFile second_file("//foo/BUILD.gn");
+  loader->Load(second_file, LocationRange(), second_tc);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
+
+  // Running the toolchain file should schedule the build config file to load
+  // for that toolchain.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // We have to tell it we have a toolchain definition now (normally the
+  // builder would do this).
+  const Settings* default_settings = loader->GetToolchainSettings(Label());
+  Toolchain second_tc_object(default_settings, second_tc);
+  loader->ToolchainLoaded(&second_tc_object);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Scheduling a second file to load in that toolchain should not make it
+  // pending yet (it's waiting for the build config).
+  SourceFile third_file("//bar/BUILD.gn");
+  loader->Load(third_file, LocationRange(), second_tc);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Running the build config file should make our third file pending.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
+
+  EXPECT_FALSE(scheduler().is_failed());
+}
+
+TEST_F(LoaderTest, BuildDependencyFilesAreCollected) {
+  SourceFile build_config("//build/config/BUILDCONFIG.gn");
+  SourceFile root_build("//BUILD.gn");
+  build_settings_.set_build_config_file(build_config);
+  build_settings_.set_item_defined_callback(
+      [builder = &mock_builder_](std::unique_ptr<Item> item) {
+        builder->OnItemDefined(std::move(item));
+      });
+
+  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
+  mock_ifm_.AddCannedResponse(build_config,
+                              "set_default_toolchain(\"//tc:tc\")");
+  std::string root_build_content =
+      "executable(\"a\") { sources = [ \"a.cc\" ] }\n"
+      "config(\"b\") { configs = [\"//t:t\"] }\n"
+      "toolchain(\"c\") {}\n"
+      "pool(\"d\") { depth = 1 }";
+  mock_ifm_.AddCannedResponse(root_build, root_build_content);
+
+  loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
+
+  // Request the root build file be loaded. This should kick off the default
+  // build config loading.
+  loader->Load(root_build, LocationRange(), Label());
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Completing the build config load should kick off the root build file load.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
+
+  // Completing the root build file should define a target which must have
+  // set of source files hashes.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  std::vector<const Item*> items = mock_builder_.GetAllItems();
+  EXPECT_TRUE(items[0]->AsTarget());
+  EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
+  EXPECT_TRUE(items[1]->AsConfig());
+  EXPECT_TRUE(ItemContainsBuildDependencyFile(items[1], root_build));
+  EXPECT_TRUE(items[2]->AsToolchain());
+  EXPECT_TRUE(ItemContainsBuildDependencyFile(items[2], root_build));
+  EXPECT_TRUE(items[3]->AsPool());
+  EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build));
+
+  EXPECT_FALSE(scheduler().is_failed());
+}
+
+TEST_F(LoaderTest, TemplateBuildDependencyFilesAreCollected) {
+  SourceFile build_config("//build/config/BUILDCONFIG.gn");
+  SourceFile root_build("//BUILD.gn");
+  build_settings_.set_build_config_file(build_config);
+  build_settings_.set_item_defined_callback(
+      [builder = &mock_builder_](std::unique_ptr<Item> item) {
+        builder->OnItemDefined(std::move(item));
+      });
+
+  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
+  mock_ifm_.AddCannedResponse(build_config,
+                              "set_default_toolchain(\"//tc:tc\")");
+  mock_ifm_.AddCannedResponse(
+      SourceFile("//test.gni"),
+      "template(\"tmpl\") {\n"
+      "  executable(target_name) { sources = invoker.sources }\n"
+      "}\n");
+  mock_ifm_.AddCannedResponse(root_build,
+                              "import(\"//test.gni\")\n"
+                              "tmpl(\"a\") {sources = [ \"a.cc\" ]}\n");
+
+  loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
+
+  // Request the root build file be loaded. This should kick off the default
+  // build config loading.
+  loader->Load(root_build, LocationRange(), Label());
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Completing the build config load should kick off the root build file load.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
+
+  // Completing the root build file should define a target which must have
+  // set of source files hashes.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  std::vector<const Item*> items = mock_builder_.GetAllItems();
+  EXPECT_TRUE(items[0]->AsTarget());
+  // Ensure the target as a dep on BUILD.gn even though it was defined via
+  // a template.
+  EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
+
+  EXPECT_FALSE(scheduler().is_failed());
+}
+
+TEST_F(LoaderTest, NonDefaultBuildFileName) {
+  std::string new_name = "BUILD.more.gn";
+
+  SourceFile build_config("//build/config/BUILDCONFIG.gn");
+  build_settings_.set_build_config_file(build_config);
+
+  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
+  loader->set_build_file_extension("more");
+
+  // The default toolchain needs to be set by the build config file.
+  mock_ifm_.AddCannedResponse(build_config,
+                              "set_default_toolchain(\"//tc:tc\")");
+
+  loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
+
+  // Request the root build file be loaded. This should kick off the default
+  // build config loading.
+  SourceFile root_build("//" + new_name);
+  loader->Load(root_build, LocationRange(), Label());
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Completing the build config load should kick off the root build file load.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
+
+  // Load the root build file.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // Schedule some other file to load in another toolchain.
+  Label second_tc(SourceDir("//tc2/"), "tc2");
+  SourceFile second_file("//foo/" + new_name);
+  loader->Load(second_file, LocationRange(), second_tc);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/" + new_name)));
+
+  // Running the toolchain file should schedule the build config file to load
+  // for that toolchain.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // We have to tell it we have a toolchain definition now (normally the
+  // builder would do this).
+  const Settings* default_settings = loader->GetToolchainSettings(Label());
+  Toolchain second_tc_object(default_settings, second_tc);
+  loader->ToolchainLoaded(&second_tc_object);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Running the build config file should make our second file pending.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(second_file));
+
+  EXPECT_FALSE(scheduler().is_failed());
+}
diff --git a/src/gn/location.cc b/src/gn/location.cc
new file mode 100644 (file)
index 0000000..95bdd13
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/location.h"
+
+#include <tuple>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/input_file.h"
+
+Location::Location() = default;
+
+Location::Location(const InputFile* file,
+                   int line_number,
+                   int column_number,
+                   int byte)
+    : file_(file),
+      line_number_(line_number),
+      column_number_(column_number),
+      byte_(byte) {}
+
+bool Location::operator==(const Location& other) const {
+  return other.file_ == file_ && other.line_number_ == line_number_ &&
+         other.column_number_ == column_number_;
+}
+
+bool Location::operator!=(const Location& other) const {
+  return !operator==(other);
+}
+
+bool Location::operator<(const Location& other) const {
+  DCHECK(file_ == other.file_);
+  return std::tie(line_number_, column_number_) <
+         std::tie(other.line_number_, other.column_number_);
+}
+
+std::string Location::Describe(bool include_column_number) const {
+  if (!file_)
+    return std::string();
+
+  std::string ret;
+  if (file_->friendly_name().empty())
+    ret = file_->name().value();
+  else
+    ret = file_->friendly_name();
+
+  ret += ":";
+  ret += base::IntToString(line_number_);
+  if (include_column_number) {
+    ret += ":";
+    ret += base::IntToString(column_number_);
+  }
+  return ret;
+}
+
+LocationRange::LocationRange() = default;
+
+LocationRange::LocationRange(const Location& begin, const Location& end)
+    : begin_(begin), end_(end) {
+  DCHECK(begin_.file() == end_.file());
+}
+
+LocationRange LocationRange::Union(const LocationRange& other) const {
+  DCHECK(begin_.file() == other.begin_.file());
+  return LocationRange(begin_ < other.begin_ ? begin_ : other.begin_,
+                       end_ < other.end_ ? other.end_ : end_);
+}
diff --git a/src/gn/location.h b/src/gn/location.h
new file mode 100644 (file)
index 0000000..471391b
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_LOCATION_H_
+#define TOOLS_GN_LOCATION_H_
+
+#include <string>
+
+class InputFile;
+
+// Represents a place in a source file. Used for error reporting.
+class Location {
+ public:
+  Location();
+  Location(const InputFile* file, int line_number, int column_number, int byte);
+
+  const InputFile* file() const { return file_; }
+  int line_number() const { return line_number_; }
+  int column_number() const { return column_number_; }
+  int byte() const { return byte_; }
+  bool is_null() const { return *this == Location(); }
+
+  bool operator==(const Location& other) const;
+  bool operator!=(const Location& other) const;
+  bool operator<(const Location& other) const;
+
+  // Returns a string with the file, line, and (optionally) the character
+  // offset for this location. If this location is null, returns an empty
+  // string.
+  std::string Describe(bool include_column_number) const;
+
+ private:
+  const InputFile* file_ = nullptr;  // Null when unset.
+  int line_number_ = -1;             // -1 when unset. 1-based.
+  int column_number_ = -1;           // -1 when unset. 1-based.
+  int byte_ = 0;                     // Index into the buffer, 0-based.
+};
+
+// Represents a range in a source file. Used for error reporting.
+// The end is exclusive i.e. [begin, end)
+class LocationRange {
+ public:
+  LocationRange();
+  LocationRange(const Location& begin, const Location& end);
+
+  const Location& begin() const { return begin_; }
+  const Location& end() const { return end_; }
+  bool is_null() const {
+    return begin_.is_null();  // No need to check both for the null case.
+  }
+
+  LocationRange Union(const LocationRange& other) const;
+
+ private:
+  Location begin_;
+  Location end_;
+};
+
+#endif  // TOOLS_GN_LOCATION_H_
diff --git a/src/gn/metadata.cc b/src/gn/metadata.cc
new file mode 100644 (file)
index 0000000..fa52081
--- /dev/null
@@ -0,0 +1,273 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/metadata.h"
+
+#include "gn/filesystem_utils.h"
+
+const char kMetadata_Help[] =
+    R"(Metadata Collection
+
+  Metadata is information attached to targets throughout the dependency tree. GN
+  allows for the collection of this data into files written during the generation
+  step, enabling users to expose and aggregate this data based on the dependency
+  tree.
+
+generated_file targets
+
+  Similar to the write_file() function, the generated_file target type
+  creates a file in the specified location with the specified content. The
+  primary difference between write_file() and this target type is that the
+  write_file function does the file write at parse time, while the
+  generated_file target type writes at target resolution time. See
+  "gn help generated_file" for more detail.
+
+  When written at target resolution time, generated_file enables GN to
+  collect and write aggregated metadata from dependents.
+
+  A generated_file target can declare either 'contents' to write statically
+  known contents to a file or 'data_keys' to aggregate metadata and write the
+  result to a file. It can also specify 'walk_keys' (to restrict the metadata
+  collection), 'output_conversion', and 'rebase'.
+
+
+Collection and Aggregation
+
+  Targets can declare a 'metadata' variable containing a scope, and this
+  metadata may be collected and written out to a file specified by
+  generated_file aggregation targets. The 'metadata' scope must contain
+  only list values since the aggregation step collects a list of these values.
+
+  During the target resolution, generated_file targets will walk their
+  dependencies recursively, collecting metadata based on the specified
+  'data_keys'. 'data_keys' is specified as a list of strings, used by the walk
+  to identify which variables in dependencies' 'metadata' scopes to collect.
+
+  The walk begins with the listed dependencies of the 'generated_file' target.
+  The 'metadata' scope for each dependency is inspected for matching elements
+  of the 'generated_file' target's 'data_keys' list.  If a match is found, the
+  data from the dependent's matching key list is appended to the aggregate walk
+  list. Note that this means that if more than one walk key is specified, the
+  data in all of them will be aggregated into one list. From there, the walk
+  will then recurse into the dependencies of each target it encounters,
+  collecting the specified metadata for each.
+
+  For example:
+
+    group("a") {
+      metadata = {
+        doom_melon = [ "enable" ]
+        my_files = [ "foo.cpp" ]
+        my_extra_files = [ "bar.cpp" ]
+      }
+
+      deps = [ ":b" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "baz.cpp" ]
+      }
+    }
+
+    generated_file("metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files", "my_extra_files" ]
+
+      deps = [ ":a" ]
+    }
+
+  The above will produce the following file data:
+
+    foo.cpp
+    bar.cpp
+    baz.cpp
+
+  The dependency walk can be limited by using the 'walk_keys'. This is a list of
+  labels that should be included in the walk. All labels specified here should
+  also be in one of the deps lists. These keys act as barriers, where the walk
+  will only recurse into the targets listed. An empty list in all specified
+  barriers will end that portion of the walk.
+
+    group("a") {
+      metadata = {
+        my_files = [ "foo.cpp" ]
+        my_files_barrier = [ ":b" ]
+      }
+
+      deps = [ ":b", ":c" ]
+    }
+
+    group("b") {
+      metadata = {
+        my_files = [ "bar.cpp" ]
+      }
+    }
+
+    group("c") {
+      metadata = {
+        my_files = [ "doom_melon.cpp" ]
+      }
+    }
+
+    generated_file("metadata") {
+      outputs = [ "$root_build_dir/my_files.json" ]
+      data_keys = [ "my_files" ]
+      walk_keys = [ "my_files_barrier" ]
+
+      deps = [ ":a" ]
+    }
+
+  The above will produce the following file data (note that `doom_melon.cpp` is
+  not included):
+
+    foo.cpp
+    bar.cpp
+
+  A common example of this sort of barrier is in builds that have host tools
+  built as part of the tree, but do not want the metadata from those host tools
+  to be collected with the target-side code.
+
+Common Uses
+
+  Metadata can be used to collect information about the different targets in the
+  build, and so a common use is to provide post-build tooling with a set of data
+  necessary to do aggregation tasks. For example, if each test target specifies
+  the output location of its binary to run in a metadata field, that can be
+  collected into a single file listing the locations of all tests in the
+  dependency tree. A local build tool (or continuous integration infrastructure)
+  can then use that file to know which tests exist, and where, and run them
+  accordingly.
+
+  Another use is in image creation, where a post-build image tool needs to know
+  various pieces of information about the components it should include in order
+  to put together the correct image.
+)";
+
+bool Metadata::WalkStep(const BuildSettings* settings,
+                        const std::vector<std::string>& keys_to_extract,
+                        const std::vector<std::string>& keys_to_walk,
+                        const SourceDir& rebase_dir,
+                        std::vector<Value>* next_walk_keys,
+                        std::vector<Value>* result,
+                        Err* err) const {
+  // If there's no metadata, there's nothing to find, so quick exit.
+  if (contents_.empty()) {
+    next_walk_keys->emplace_back(nullptr, "");
+    return true;
+  }
+
+  // Pull the data from each specified key.
+  for (const auto& key : keys_to_extract) {
+    auto iter = contents_.find(key);
+    if (iter == contents_.end())
+      continue;
+    assert(iter->second.type() == Value::LIST);
+
+    if (!rebase_dir.is_null()) {
+      for (const auto& val : iter->second.list_value()) {
+        std::pair<Value, bool> pair =
+            this->RebaseValue(settings, rebase_dir, val, err);
+        if (!pair.second)
+          return false;
+        result->push_back(pair.first);
+      }
+    } else {
+      result->insert(result->end(), iter->second.list_value().begin(),
+                     iter->second.list_value().end());
+    }
+  }
+
+  // Get the targets to look at next. If no keys_to_walk are present, we push
+  // the empty string to the list so that the target knows to include its deps
+  // and data_deps. The values used here must be lists of strings.
+  bool found_walk_key = false;
+  for (const auto& key : keys_to_walk) {
+    auto iter = contents_.find(key);
+    if (iter != contents_.end()) {
+      found_walk_key = true;
+      assert(iter->second.type() == Value::LIST);
+      for (const auto& val : iter->second.list_value()) {
+        if (!val.VerifyTypeIs(Value::STRING, err))
+          return false;
+        next_walk_keys->emplace_back(val);
+      }
+    }
+  }
+
+  if (!found_walk_key)
+    next_walk_keys->emplace_back(nullptr, "");
+
+  return true;
+}
+
+std::pair<Value, bool> Metadata::RebaseValue(const BuildSettings* settings,
+                                             const SourceDir& rebase_dir,
+                                             const Value& value,
+                                             Err* err) const {
+  switch (value.type()) {
+    case Value::STRING:
+      return this->RebaseStringValue(settings, rebase_dir, value, err);
+    case Value::LIST:
+      return this->RebaseListValue(settings, rebase_dir, value, err);
+    case Value::SCOPE:
+      return this->RebaseScopeValue(settings, rebase_dir, value, err);
+    default:
+      return std::make_pair(value, true);
+  }
+}
+
+std::pair<Value, bool> Metadata::RebaseStringValue(
+    const BuildSettings* settings,
+    const SourceDir& rebase_dir,
+    const Value& value,
+    Err* err) const {
+  if (!value.VerifyTypeIs(Value::STRING, err))
+    return std::make_pair(value, false);
+  std::string filename = source_dir_.ResolveRelativeAs(
+      /*as_file = */ true, value, err, settings->root_path_utf8());
+  if (err->has_error())
+    return std::make_pair(value, false);
+  Value rebased_value(value.origin(), RebasePath(filename, rebase_dir,
+                                                 settings->root_path_utf8()));
+  return std::make_pair(rebased_value, true);
+}
+
+std::pair<Value, bool> Metadata::RebaseListValue(const BuildSettings* settings,
+                                                 const SourceDir& rebase_dir,
+                                                 const Value& value,
+                                                 Err* err) const {
+  if (!value.VerifyTypeIs(Value::LIST, err))
+    return std::make_pair(value, false);
+
+  Value rebased_list_value(value.origin(), Value::LIST);
+  for (auto& val : value.list_value()) {
+    std::pair<Value, bool> pair = RebaseValue(settings, rebase_dir, val, err);
+    if (!pair.second)
+      return std::make_pair(value, false);
+    rebased_list_value.list_value().push_back(pair.first);
+  }
+  return std::make_pair(rebased_list_value, true);
+}
+
+std::pair<Value, bool> Metadata::RebaseScopeValue(const BuildSettings* settings,
+                                                  const SourceDir& rebase_dir,
+                                                  const Value& value,
+                                                  Err* err) const {
+  if (!value.VerifyTypeIs(Value::SCOPE, err))
+    return std::make_pair(value, false);
+  Value rebased_scope_value(value);
+  Scope::KeyValueMap scope_values;
+  value.scope_value()->GetCurrentScopeValues(&scope_values);
+  for (auto& value_pair : scope_values) {
+    std::pair<Value, bool> pair =
+        RebaseValue(settings, rebase_dir, value_pair.second, err);
+    if (!pair.second)
+      return std::make_pair(value, false);
+
+    rebased_scope_value.scope_value()->SetValue(value_pair.first, pair.first,
+                                                value.origin());
+  }
+  return std::make_pair(rebased_scope_value, true);
+}
diff --git a/src/gn/metadata.h b/src/gn/metadata.h
new file mode 100644 (file)
index 0000000..c0a0042
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_METADATA_H_
+#define TOOLS_GN_METADATA_H_
+
+#include <memory>
+
+#include "gn/build_settings.h"
+#include "gn/scope.h"
+#include "gn/source_dir.h"
+
+extern const char kMetadata_Help[];
+
+// Metadata about a particular target.
+//
+// Metadata is a collection of keys and values relating to a particular target.
+// Generally, these keys will include three categories of strings: ordinary
+// strings, filenames intended to be rebased according to their particular
+// source directory, and target labels intended to be used as barriers to the
+// walk. Verification of these categories occurs at walk time, not creation
+// time (since it is not clear until the walk which values are intended for
+// which purpose).
+//
+// Represented as a scope in the expression language, here it is reduced to just
+// the KeyValueMap (since it doesn't need the logical overhead of a full scope).
+// Values must be lists of strings, as the walking collection logic contatenates
+// their values across targets.
+class Metadata {
+ public:
+  using Contents = Scope::KeyValueMap;
+
+  Metadata() = default;
+
+  const ParseNode* origin() const { return origin_; }
+  void set_origin(const ParseNode* origin) { origin_ = origin; }
+
+  // The contents of this metadata variable.
+  const Contents& contents() const { return contents_; }
+  Contents& contents() { return contents_; }
+  void set_contents(Contents&& contents) { contents_ = std::move(contents); }
+
+  // The relative source directory to use when rebasing.
+  const SourceDir& source_dir() const { return source_dir_; }
+  SourceDir& source_dir() { return source_dir_; }
+  void set_source_dir(const SourceDir& d) { source_dir_ = d; }
+
+  // Collect the specified metadata from this instance.
+  //
+  // Calling this will populate `next_walk_keys` with the values of targets to
+  // be walked next (with the empty string "" indicating that the target should
+  // walk all of its deps and data_deps).
+  bool WalkStep(const BuildSettings* settings,
+                const std::vector<std::string>& keys_to_extract,
+                const std::vector<std::string>& keys_to_walk,
+                const SourceDir& rebase_dir,
+                std::vector<Value>* next_walk_keys,
+                std::vector<Value>* result,
+                Err* err) const;
+
+ private:
+  const ParseNode* origin_ = nullptr;
+  Contents contents_;
+  SourceDir source_dir_;
+
+  std::pair<Value, bool> RebaseValue(const BuildSettings* settings,
+                                     const SourceDir& rebase_dir,
+                                     const Value& value,
+                                     Err* err) const;
+
+  std::pair<Value, bool> RebaseStringValue(const BuildSettings* settings,
+                                           const SourceDir& rebase_dir,
+                                           const Value& value,
+                                           Err* err) const;
+
+  std::pair<Value, bool> RebaseListValue(const BuildSettings* settings,
+                                         const SourceDir& rebase_dir,
+                                         const Value& value,
+                                         Err* err) const;
+
+  std::pair<Value, bool> RebaseScopeValue(const BuildSettings* settings,
+                                          const SourceDir& rebase_dir,
+                                          const Value& value,
+                                          Err* err) const;
+
+  DISALLOW_COPY_AND_ASSIGN(Metadata);
+};
+
+#endif  // TOOLS_GN_METADATA_H_
diff --git a/src/gn/metadata_unittest.cc b/src/gn/metadata_unittest.cc
new file mode 100644 (file)
index 0000000..df57cd5
--- /dev/null
@@ -0,0 +1,233 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/metadata.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(MetadataTest, SetContents) {
+  Metadata metadata;
+
+  ASSERT_TRUE(metadata.contents().empty());
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+
+  Metadata::Contents contents;
+  contents.insert(std::pair<std::string_view, Value>("a", a_expected));
+  contents.insert(std::pair<std::string_view, Value>("b", b_expected));
+
+  metadata.set_contents(std::move(contents));
+
+  ASSERT_EQ(metadata.contents().size(), 2);
+  auto a_actual = metadata.contents().find("a");
+  auto b_actual = metadata.contents().find("b");
+  ASSERT_FALSE(a_actual == metadata.contents().end());
+  ASSERT_FALSE(b_actual == metadata.contents().end());
+  ASSERT_EQ(a_actual->second, a_expected);
+  ASSERT_EQ(b_actual->second, b_expected);
+}
+
+TEST(MetadataTest, Walk) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo.cpp"));
+  a_expected.list_value().push_back(Value(nullptr, "bar.h"));
+
+  metadata.contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected;
+  expected.emplace_back(Value(nullptr, "foo.cpp"));
+  expected.emplace_back(Value(nullptr, "bar.h"));
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir(), &next_walk_keys,
+                                &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_EQ(results, expected);
+}
+
+TEST(MetadataTest, WalkWithRebase) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo.cpp"));
+  a_expected.list_value().push_back(Value(nullptr, "foo/bar.h"));
+
+  metadata.contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected;
+  expected.emplace_back(Value(nullptr, "../home/files/foo.cpp"));
+  expected.emplace_back(Value(nullptr, "../home/files/foo/bar.h"));
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir("/usr/foo_dir/"),
+                                &next_walk_keys, &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_EQ(results, expected);
+}
+
+TEST(MetadataTest, WalkWithRebaseNonString) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a(nullptr, Value::LIST);
+  Value inner_list(nullptr, Value::LIST);
+  Value inner_scope(nullptr, Value::SCOPE);
+  inner_list.list_value().push_back(Value(nullptr, "foo.cpp"));
+  inner_list.list_value().push_back(Value(nullptr, "foo/bar.h"));
+  a.list_value().push_back(inner_list);
+
+  std::unique_ptr<Scope> scope(new Scope(setup.settings()));
+  scope->SetValue("a1", Value(nullptr, "foo2.cpp"), nullptr);
+  scope->SetValue("a2", Value(nullptr, "foo/bar2.h"), nullptr);
+  inner_scope.SetScopeValue(std::move(scope));
+  a.list_value().push_back(inner_scope);
+
+  metadata.contents().insert(std::pair<std::string_view, Value>("a", a));
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected;
+  Value inner_list_expected(nullptr, Value::LIST);
+  Value inner_scope_expected(nullptr, Value::SCOPE);
+  inner_list_expected.list_value().push_back(
+      Value(nullptr, "../home/files/foo.cpp"));
+  inner_list_expected.list_value().push_back(
+      Value(nullptr, "../home/files/foo/bar.h"));
+  expected.push_back(inner_list_expected);
+
+  std::unique_ptr<Scope> scope_expected(new Scope(setup.settings()));
+  scope_expected->SetValue("a1", Value(nullptr, "../home/files/foo2.cpp"),
+                           nullptr);
+  scope_expected->SetValue("a2", Value(nullptr, "../home/files/foo/bar2.h"),
+                           nullptr);
+  inner_scope_expected.SetScopeValue(std::move(scope_expected));
+  expected.push_back(inner_scope_expected);
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir("/usr/foo_dir/"),
+                                &next_walk_keys, &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_EQ(results, expected);
+}
+
+TEST(MetadataTest, WalkKeysToWalk) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "//target"));
+
+  metadata.contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  walk_keys.emplace_back("a");
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "//target");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir(), &next_walk_keys,
+                                &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}
+
+TEST(MetadataTest, WalkNoContents) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir(), &next_walk_keys,
+                                &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}
+
+TEST(MetadataTest, WalkNoKeysWithContents) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "//target"));
+
+  metadata.contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, SourceDir(), &next_walk_keys,
+                                &results, &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}
diff --git a/src/gn/metadata_walk.cc b/src/gn/metadata_walk.cc
new file mode 100644 (file)
index 0000000..19011cf
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/metadata_walk.h"
+
+std::vector<Value> WalkMetadata(
+    const UniqueVector<const Target*>& targets_to_walk,
+    const std::vector<std::string>& keys_to_extract,
+    const std::vector<std::string>& keys_to_walk,
+    const SourceDir& rebase_dir,
+    std::set<const Target*>* targets_walked,
+    Err* err) {
+  std::vector<Value> result;
+  for (const auto* target : targets_to_walk) {
+    auto pair = targets_walked->insert(target);
+    if (pair.second) {
+      if (!target->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir, false,
+                               &result, targets_walked, err))
+        return std::vector<Value>();
+    }
+  }
+  return result;
+}
\ No newline at end of file
diff --git a/src/gn/metadata_walk.h b/src/gn/metadata_walk.h
new file mode 100644 (file)
index 0000000..011ed07
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_METADATAWALK_H_
+#define TOOLS_GN_METADATAWALK_H_
+
+#include "gn/build_settings.h"
+#include "gn/target.h"
+#include "gn/unique_vector.h"
+#include "gn/value.h"
+
+// Function to collect metadata from resolved targets listed in targets_walked.
+// Intended to be called after all targets are resolved.
+//
+// This populates targets_walked with all targets touched by this walk, and
+// returns the list of metadata values.
+std::vector<Value> WalkMetadata(
+    const UniqueVector<const Target*>& targets_to_walk,
+    const std::vector<std::string>& keys_to_extract,
+    const std::vector<std::string>& keys_to_walk,
+    const SourceDir& rebase_dir,
+    std::set<const Target*>* targets_walked,
+    Err* err);
+
+#endif  // TOOLS_GN_METADATAWALK_H_
diff --git a/src/gn/metadata_walk_unittest.cc b/src/gn/metadata_walk_unittest.cc
new file mode 100644 (file)
index 0000000..4fcb31d
--- /dev/null
@@ -0,0 +1,211 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/metadata_walk.h"
+
+#include "gn/metadata.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "gn/unique_vector.h"
+#include "util/test/test.h"
+
+TEST(MetadataWalkTest, CollectNoRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("b", b_expected));
+
+  one.metadata().set_source_dir(SourceDir("/usr/home/files/"));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_2_expected));
+
+  Value b_2_expected(nullptr, Value::LIST);
+  b_2_expected.list_value().push_back(Value(nullptr, false));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("b", b_2_expected));
+
+  two.metadata().set_source_dir(SourceDir("/usr/home/files/inner"));
+
+  UniqueVector<const Target*> targets;
+  targets.push_back(&one);
+  targets.push_back(&two);
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::set<const Target*> targets_walked;
+  std::vector<Value> result = WalkMetadata(targets, data_keys, walk_keys,
+                                           SourceDir(), &targets_walked, &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  expected.push_back(Value(nullptr, "bar"));
+  expected.push_back(Value(nullptr, false));
+  EXPECT_EQ(result, expected);
+
+  std::set<const Target*> expected_walked_targets;
+  expected_walked_targets.insert(&one);
+  expected_walked_targets.insert(&two);
+  EXPECT_EQ(targets_walked, expected_walked_targets);
+}
+
+TEST(MetadataWalkTest, CollectWithRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("b", b_expected));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_2_expected));
+
+  one.public_deps().push_back(LabelTargetPair(&two));
+
+  UniqueVector<const Target*> targets;
+  targets.push_back(&one);
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::set<const Target*> targets_walked;
+  std::vector<Value> result = WalkMetadata(targets, data_keys, walk_keys,
+                                           SourceDir(), &targets_walked, &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "bar"));
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  EXPECT_EQ(result, expected);
+
+  std::set<const Target*> expected_walked_targets;
+  expected_walked_targets.insert(&one);
+  expected_walked_targets.insert(&two);
+  EXPECT_EQ(targets_walked, expected_walked_targets);
+}
+
+TEST(MetadataWalkTest, CollectWithBarrier) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(
+      Value(nullptr, "//foo:two(//toolchain:default)"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("walk", walk_expected));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_2_expected));
+
+  TestTarget three(setup, "//foo:three", Target::SOURCE_SET);
+  Value a_3_expected(nullptr, Value::LIST);
+  a_3_expected.list_value().push_back(Value(nullptr, "baz"));
+  three.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_3_expected));
+
+  one.public_deps().push_back(LabelTargetPair(&two));
+  one.public_deps().push_back(LabelTargetPair(&three));
+
+  UniqueVector<const Target*> targets;
+  targets.push_back(&one);
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::set<const Target*> targets_walked;
+  std::vector<Value> result = WalkMetadata(targets, data_keys, walk_keys,
+                                           SourceDir(), &targets_walked, &err);
+  EXPECT_FALSE(err.has_error()) << err.message();
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "bar"));
+  expected.push_back(Value(nullptr, "foo"));
+  EXPECT_EQ(result, expected) << result.size();
+
+  std::set<const Target*> expected_walked_targets;
+  expected_walked_targets.insert(&one);
+  expected_walked_targets.insert(&two);
+  EXPECT_EQ(targets_walked, expected_walked_targets);
+}
+
+TEST(MetadataWalkTest, CollectWithError) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(Value(nullptr, "//foo:missing"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("walk", walk_expected));
+
+  UniqueVector<const Target*> targets;
+  targets.push_back(&one);
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::set<const Target*> targets_walked;
+  std::vector<Value> result = WalkMetadata(targets, data_keys, walk_keys,
+                                           SourceDir(), &targets_walked, &err);
+  EXPECT_TRUE(result.empty());
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(err.message(),
+            "I was expecting //foo:missing(//toolchain:default) to be a "
+            "dependency of //foo:one(//toolchain:default). "
+            "Make sure it's included in the deps or data_deps, and that you've "
+            "specified the appropriate toolchain.")
+      << err.message();
+}
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc
new file mode 100644 (file)
index 0000000..bfceef9
--- /dev/null
@@ -0,0 +1,250 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_action_target_writer.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_util.h"
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/general_tool.h"
+#include "gn/pool.h"
+#include "gn/settings.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+NinjaActionTargetWriter::NinjaActionTargetWriter(const Target* target,
+                                                 std::ostream& out)
+    : NinjaTargetWriter(target, out),
+      path_output_no_escaping_(
+          target->settings()->build_settings()->build_dir(),
+          target->settings()->build_settings()->root_path_utf8(),
+          ESCAPE_NONE) {}
+
+NinjaActionTargetWriter::~NinjaActionTargetWriter() = default;
+
+void NinjaActionTargetWriter::Run() {
+  std::string custom_rule_name = WriteRuleDefinition();
+
+  // Collect our deps to pass as additional "hard dependencies" for input deps.
+  // This will force all of the action's dependencies to be completed before
+  // the action is run. Usually, if an action has a dependency, it will be
+  // operating on the result of that previous step, so we need to be sure to
+  // serialize these.
+  std::vector<const Target*> additional_hard_deps;
+  std::vector<OutputFile> data_outs;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_outs.push_back(pair.ptr->dependency_output_file());
+    } else {
+      additional_hard_deps.push_back(pair.ptr);
+    }
+  }
+
+  // For ACTIONs, the input deps appear only once in the generated ninja
+  // file, so WriteInputDepsStampAndGetDep() won't create a stamp file
+  // and the action will just depend on all the input deps directly.
+  size_t num_stamp_uses =
+      target_->output_type() == Target::ACTION ? 1u : target_->sources().size();
+  std::vector<OutputFile> input_deps =
+      WriteInputDepsStampAndGetDep(additional_hard_deps, num_stamp_uses);
+  out_ << std::endl;
+
+  // Collects all output files for writing below.
+  std::vector<OutputFile> output_files;
+
+  if (target_->output_type() == Target::ACTION_FOREACH) {
+    // Write separate build lines for each input source file.
+    WriteSourceRules(custom_rule_name, input_deps, &output_files);
+  } else {
+    DCHECK(target_->output_type() == Target::ACTION);
+
+    // Write a rule that invokes the script once with the outputs as outputs,
+    // and the data as inputs. It does not depend on the sources.
+    out_ << "build";
+    SubstitutionWriter::GetListAsOutputFiles(
+        settings_, target_->action_values().outputs(), &output_files);
+    path_output_.WriteFiles(out_, output_files);
+
+    out_ << ": " << custom_rule_name;
+    if (!input_deps.empty()) {
+      // As in WriteSourceRules, we want to force this target to rebuild any
+      // time any of its dependencies change.
+      out_ << " |";
+      path_output_.WriteFiles(out_, input_deps);
+    }
+    out_ << std::endl;
+    if (target_->action_values().has_depfile()) {
+      WriteDepfile(SourceFile());
+    }
+    if (target_->action_values().pool().ptr) {
+      out_ << "  pool = ";
+      out_ << target_->action_values().pool().ptr->GetNinjaName(
+          settings_->default_toolchain_label());
+      out_ << std::endl;
+    }
+  }
+  out_ << std::endl;
+
+  // Write the stamp, which also depends on all data deps. These are needed at
+  // runtime and should be compiled when the action is, but don't need to be
+  // done before we run the action.
+  // TODO(thakis): If the action has just a single output, make things depend
+  // on that output directly without writing a stamp file.
+  for (const auto& dep : target_->data_deps())
+    data_outs.push_back(dep.ptr->dependency_output_file());
+  WriteStampForTarget(output_files, data_outs);
+}
+
+std::string NinjaActionTargetWriter::WriteRuleDefinition() {
+  // Make a unique name for this rule.
+  //
+  // Use a unique name for the response file when there are multiple build
+  // steps so that they don't stomp on each other. When there are no sources,
+  // there will be only one invocation so we can use a simple name.
+  std::string target_label = target_->label().GetUserVisibleName(true);
+  std::string custom_rule_name(target_label);
+  base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
+  custom_rule_name.append("_rule");
+
+  const SubstitutionList& args = target_->action_values().args();
+  EscapeOptions args_escape_options;
+  args_escape_options.mode = ESCAPE_NINJA_COMMAND;
+
+  out_ << "rule " << custom_rule_name << std::endl;
+
+  if (target_->action_values().uses_rsp_file()) {
+    // Needs a response file. The unique_name part is for action_foreach so
+    // each invocation of the rule gets a different response file. This isn't
+    // strictly necessary for regular one-shot actions, but it's easier to
+    // just always define unique_name.
+    std::string rspfile = custom_rule_name;
+    if (!target_->sources().empty())
+      rspfile += ".$unique_name";
+    rspfile += ".rsp";
+    out_ << "  rspfile = " << rspfile << std::endl;
+
+    // Response file contents.
+    out_ << "  rspfile_content =";
+    for (const auto& arg :
+         target_->action_values().rsp_file_contents().list()) {
+      out_ << " ";
+      SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options,
+                                                  out_);
+    }
+    out_ << std::endl;
+  }
+
+  out_ << "  command = ";
+  path_output_.WriteFile(out_, settings_->build_settings()->python_path());
+  out_ << " ";
+  path_output_.WriteFile(out_, target_->action_values().script());
+  for (const auto& arg : args.list()) {
+    out_ << " ";
+    SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
+  }
+  out_ << std::endl;
+  out_ << "  description = ACTION " << target_label << std::endl;
+  out_ << "  restat = 1" << std::endl;
+  const Tool* tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolAction);
+  if (tool && tool->pool().ptr) {
+    out_ << "  pool = ";
+    out_ << tool->pool().ptr->GetNinjaName(
+        settings_->default_toolchain_label());
+    out_ << std::endl;
+  }
+
+  return custom_rule_name;
+}
+
+void NinjaActionTargetWriter::WriteSourceRules(
+    const std::string& custom_rule_name,
+    const std::vector<OutputFile>& input_deps,
+    std::vector<OutputFile>* output_files) {
+  EscapeOptions args_escape_options;
+  args_escape_options.mode = ESCAPE_NINJA_COMMAND;
+  // We're writing the substitution values, these should not be quoted since
+  // they will get pasted into the real command line.
+  args_escape_options.inhibit_quoting = true;
+
+  const Target::FileList& sources = target_->sources();
+  for (size_t i = 0; i < sources.size(); i++) {
+    out_ << "build";
+    WriteOutputFilesForBuildLine(sources[i], output_files);
+
+    out_ << ": " << custom_rule_name << " ";
+    path_output_.WriteFile(out_, sources[i]);
+    if (!input_deps.empty()) {
+      // Using "|" for the dependencies forces all implicit dependencies to be
+      // fully up to date before running the action, and will re-run this
+      // action if any input dependencies change. This is important because
+      // this action may consume the outputs of previous steps.
+      out_ << " |";
+      path_output_.WriteFiles(out_, input_deps);
+    }
+    out_ << std::endl;
+
+    // Response files require a unique name be defined.
+    if (target_->action_values().uses_rsp_file())
+      out_ << "  unique_name = " << i << std::endl;
+
+    // The required types is the union of the args and response file. This
+    // might theoretically duplicate a definition if the same substitution is
+    // used in both the args and the response file. However, this should be
+    // very unusual (normally the substitutions will go in one place or the
+    // other) and the redundant assignment won't bother Ninja.
+    SubstitutionWriter::WriteNinjaVariablesForSource(
+        target_, settings_, sources[i],
+        target_->action_values().args().required_types(), args_escape_options,
+        out_);
+    SubstitutionWriter::WriteNinjaVariablesForSource(
+        target_, settings_, sources[i],
+        target_->action_values().rsp_file_contents().required_types(),
+        args_escape_options, out_);
+
+    if (target_->action_values().has_depfile()) {
+      WriteDepfile(sources[i]);
+    }
+    if (target_->action_values().pool().ptr) {
+      out_ << "  pool = ";
+      out_ << target_->action_values().pool().ptr->GetNinjaName(
+          settings_->default_toolchain_label());
+      out_ << std::endl;
+    }
+  }
+}
+
+void NinjaActionTargetWriter::WriteOutputFilesForBuildLine(
+    const SourceFile& source,
+    std::vector<OutputFile>* output_files) {
+  size_t first_output_index = output_files->size();
+
+  SubstitutionWriter::ApplyListToSourceAsOutputFile(
+      target_, settings_, target_->action_values().outputs(), source,
+      output_files);
+
+  for (size_t i = first_output_index; i < output_files->size(); i++) {
+    out_ << " ";
+    path_output_.WriteFile(out_, (*output_files)[i]);
+  }
+}
+
+void NinjaActionTargetWriter::WriteDepfile(const SourceFile& source) {
+  out_ << "  depfile = ";
+  path_output_.WriteFile(
+      out_,
+      SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
+          target_, settings_, target_->action_values().depfile(), source));
+  out_ << std::endl;
+  // Using "deps = gcc" allows Ninja to read and store the depfile content in
+  // its internal database which improves performance, especially for large
+  // depfiles. The use of this feature with depfiles that contain multiple
+  // outputs require Ninja version 1.9.0 or newer.
+  if (settings_->build_settings()->ninja_required_version() >=
+      Version{1, 9, 0}) {
+    out_ << "  deps = gcc" << std::endl;
+  }
+}
diff --git a/src/gn/ninja_action_target_writer.h b/src/gn/ninja_action_target_writer.h
new file mode 100644 (file)
index 0000000..eb94284
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_ACTION_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_ACTION_TARGET_WRITER_H_
+
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+class OutputFile;
+
+// Writes a .ninja file for a action target type.
+class NinjaActionTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaActionTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaActionTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter,
+                           WriteOutputFilesForBuildLine);
+  FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter,
+                           WriteOutputFilesForBuildLineWithDepfile);
+  FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter, WriteArgsSubstitutions);
+
+  // Writes the Ninja rule for invoking the script.
+  //
+  // Returns the name of the custom rule generated. This will be based on the
+  // target name, and will include the string "$unique_name" if there are
+  // multiple inputs.
+  std::string WriteRuleDefinition();
+
+  // Writes the rules for compiling each source, writing all output files
+  // to the given vector.
+  //
+  // input_deps are the dependencies common to all build steps.
+  void WriteSourceRules(const std::string& custom_rule_name,
+                        const std::vector<OutputFile>& input_deps,
+                        std::vector<OutputFile>* output_files);
+
+  // Writes the output files generated by the output template for the given
+  // source file. This will start with a space and will not include a newline.
+  // Appends the output files to the given vector.
+  void WriteOutputFilesForBuildLine(const SourceFile& source,
+                                    std::vector<OutputFile>* output_files);
+
+  void WriteDepfile(const SourceFile& source);
+
+  // Path output writer that doesn't do any escaping or quoting. It does,
+  // however, convert slashes.  Used for
+  // computing intermediate strings.
+  PathOutput path_output_no_escaping_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaActionTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_ACTION_TARGET_WRITER_H_
diff --git a/src/gn/ninja_action_target_writer_unittest.cc b/src/gn/ninja_action_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..80b125b
--- /dev/null
@@ -0,0 +1,488 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <sstream>
+
+#include "gn/ninja_action_target_writer.h"
+#include "gn/pool.h"
+#include "gn/substitution_list.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/gen/a b{{source_name_part}}.h",
+                                    "//out/Debug/gen/{{source_name_part}}.cc");
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+
+  SourceFile source("//foo/bar.in");
+  std::vector<OutputFile> output_files;
+  writer.WriteOutputFilesForBuildLine(source, &output_files);
+
+  EXPECT_EQ(" gen/a$ bbar.h gen/bar.cc", out.str());
+}
+
+// Tests an action with no sources.
+TEST(NinjaActionTargetWriter, ActionNoSources) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION);
+
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+  target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
+
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char* expected = R"(rule __foo_bar___rule
+  command = /usr/bin/python ../../foo/script.py
+  description = ACTION //foo:bar()
+  restat = 1
+
+build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
+
+build obj/foo/bar.stamp: stamp foo.out
+)";
+  EXPECT_EQ(expected, out.str()) << expected << "--" << out.str();
+}
+
+// Tests an action with no sources and pool
+TEST(NinjaActionTargetWriter, ActionNoSourcesConsole) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION);
+
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+  target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
+
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+
+  Pool pool(setup.settings(),
+            Label(SourceDir("//"), "console", setup.toolchain()->label().dir(),
+                  setup.toolchain()->label().name()));
+  pool.set_depth(1);
+  target.action_values().set_pool(LabelPtrPair<Pool>(&pool));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  // The console pool's name must be mapped exactly to the string "console"
+  // which is a special pre-defined pool name in ninja.
+  const char* expected = R"(rule __foo_bar___rule
+  command = /usr/bin/python ../../foo/script.py
+  description = ACTION //foo:bar()
+  restat = 1
+
+build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
+  pool = console
+
+build obj/foo/bar.stamp: stamp foo.out
+)";
+  EXPECT_EQ(expected, out.str());
+}
+
+// Makes sure that we write sources as input dependencies for actions with
+// both sources and inputs (ACTION_FOREACH treats the sources differently).
+TEST(NinjaActionTargetWriter, ActionWithSources) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION);
+
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.sources().push_back(SourceFile("//foo/source.txt"));
+  target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
+
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py\n"
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "\n"
+      "build foo.out: __foo_bar___rule | ../../foo/script.py "
+      "../../foo/included.txt ../../foo/source.txt\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp foo.out\n";
+  EXPECT_EQ(expected_linux, out.str());
+}
+
+TEST(NinjaActionTargetWriter, ForEach) {
+  Err err;
+  TestWithScope setup;
+
+  // Some dependencies that the action can depend on. Use actions for these
+  // so they have a nice platform-independent stamp file that can appear in the
+  // output (rather than having to worry about how the current platform names
+  // binaries).
+  Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
+  dep.set_output_type(Target::ACTION);
+  dep.visibility().SetPublic();
+  dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
+  datadep.set_output_type(Target::ACTION);
+  datadep.visibility().SetPublic();
+  datadep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+  target.private_deps().push_back(LabelTargetPair(&dep));
+  target.private_deps().push_back(LabelTargetPair(&bundle_data_dep));
+  target.data_deps().push_back(LabelTargetPair(&datadep));
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.sources().push_back(SourceFile("//foo/input2.txt"));
+
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.action_values().args() = SubstitutionList::MakeForTest(
+      "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py -i ${in} "
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+      "\"--out=foo$ bar${source_name_part}.o\"\n"
+#else
+      "--out=foo\\$ bar${source_name_part}.o\n"
+#endif
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+      "../../foo/included.txt obj/foo/dep.stamp\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
+      "obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input1\n"
+      "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
+      "obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input2\n"
+      "\n"
+      "build obj/foo/bar.stamp: "
+      "stamp input1.out input2.out || obj/foo/bundle_data_dep.stamp "
+      "obj/foo/datadep.stamp\n";
+
+  std::string out_str = out.str();
+#if defined(OS_WIN)
+  std::replace(out_str.begin(), out_str.end(), '\\', '/');
+#endif
+  EXPECT_EQ(expected_linux, out_str);
+}
+
+TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.sources().push_back(SourceFile("//foo/input2.txt"));
+
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  SubstitutionPattern depfile;
+  ASSERT_TRUE(
+      depfile.Parse("//out/Debug/gen/{{source_name_part}}.d", nullptr, &err));
+  target.action_values().set_depfile(depfile);
+
+  target.action_values().args() = SubstitutionList::MakeForTest(
+      "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+  setup.build_settings()->set_ninja_required_version(Version{1, 9, 0});
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py -i ${in} "
+#if defined(OS_WIN)
+      "\"--out=foo$ bar${source_name_part}.o\"\n"
+#else
+      "--out=foo\\$ bar${source_name_part}.o\n"
+#endif
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+      "../../foo/included.txt\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt"
+      " | obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input1\n"
+      "  depfile = gen/input1.d\n"
+      "  deps = gcc\n"
+      "build input2.out: __foo_bar___rule ../../foo/input2.txt"
+      " | obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input2\n"
+      "  depfile = gen/input2.d\n"
+      "  deps = gcc\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
+  EXPECT_EQ(expected_linux, out.str());
+}
+
+TEST(NinjaActionTargetWriter, ForEachWithResponseFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Make sure we get interesting substitutions for both the args and the
+  // response file contents.
+  target.action_values().args() = SubstitutionList::MakeForTest(
+      "{{source}}", "{{source_file_part}}", "{{response_file_name}}");
+  target.action_values().rsp_file_contents() =
+      SubstitutionList::MakeForTest("-j", "{{source_name_part}}");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      // This name is autogenerated from the target rule name.
+      "  rspfile = __foo_bar___rule.$unique_name.rsp\n"
+      // These come from rsp_file_contents above.
+      "  rspfile_content = -j ${source_name_part}\n"
+      // These come from the args.
+      "  command = /usr/bin/python ../../foo/script.py ${in} "
+      "${source_file_part} ${rspfile}\n"
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt"
+      " | ../../foo/script.py\n"
+      // Necessary for the rspfile defined in the rule.
+      "  unique_name = 0\n"
+      // Substitution for the args.
+      "  source_file_part = input1.txt\n"
+      // Substitution for the rspfile contents.
+      "  source_name_part = input1\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out\n";
+  EXPECT_EQ(expected_linux, out.str());
+}
+
+TEST(NinjaActionTargetWriter, ForEachWithPool) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  Pool pool(setup.settings(),
+            Label(SourceDir("//foo/"), "pool", setup.toolchain()->label().dir(),
+                  setup.toolchain()->label().name()));
+  pool.set_depth(5);
+  target.action_values().set_pool(LabelPtrPair<Pool>(&pool));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Make sure we get interesting substitutions for both the args and the
+  // response file contents.
+  target.action_values().args() =
+      SubstitutionList::MakeForTest("{{source}}", "{{source_file_part}}");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      // These come from the args.
+      "  command = /usr/bin/python ../../foo/script.py ${in} "
+      "${source_file_part}\n"
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt"
+      " | ../../foo/script.py\n"
+      // Substitution for the args.
+      "  source_file_part = input1.txt\n"
+      "  pool = foo_pool\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out\n";
+  EXPECT_EQ(expected_linux, out.str());
+}
+
+TEST(NinjaActionTargetWriter, NoTransitiveHardDeps) {
+  Err err;
+  TestWithScope setup;
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
+  dep.set_output_type(Target::ACTION);
+  dep.visibility().SetPublic();
+  dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target foo(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+  foo.set_output_type(Target::ACTION);
+  foo.visibility().SetPublic();
+  foo.sources().push_back(SourceFile("//foo/input1.txt"));
+  foo.action_values().set_script(SourceFile("//foo/script.py"));
+  foo.private_deps().push_back(LabelTargetPair(&dep));
+  foo.SetToolchain(setup.toolchain());
+  foo.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+  ASSERT_TRUE(foo.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaActionTargetWriter writer(&foo, out);
+    writer.Run();
+
+    const char expected_linux[] =
+        "rule __foo_foo___rule\n"
+        // These come from the args.
+        "  command = /usr/bin/python ../../foo/script.py\n"
+        "  description = ACTION //foo:foo()\n"
+        "  restat = 1\n"
+        "\n"
+        "build foo.out: __foo_foo___rule | ../../foo/script.py"
+        " ../../foo/input1.txt obj/foo/dep.stamp\n"
+        "\n"
+        "build obj/foo/foo.stamp: stamp foo.out\n";
+    EXPECT_EQ(expected_linux, out.str());
+  }
+
+  Target bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+  bar.set_output_type(Target::ACTION);
+  bar.sources().push_back(SourceFile("//bar/input1.txt"));
+  bar.action_values().set_script(SourceFile("//bar/script.py"));
+  bar.private_deps().push_back(LabelTargetPair(&foo));
+  bar.SetToolchain(setup.toolchain());
+  bar.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/bar.out");
+  ASSERT_TRUE(bar.OnResolved(&err)) << err.message();
+
+  {
+    std::ostringstream out;
+    NinjaActionTargetWriter writer(&bar, out);
+    writer.Run();
+
+    const char expected_linux[] =
+        "rule __bar_bar___rule\n"
+        // These come from the args.
+        "  command = /usr/bin/python ../../bar/script.py\n"
+        "  description = ACTION //bar:bar()\n"
+        "  restat = 1\n"
+        "\n"
+        // Do not have obj/foo/dep.stamp as dependency.
+        "build bar.out: __bar_bar___rule | ../../bar/script.py"
+        " ../../bar/input1.txt obj/foo/foo.stamp\n"
+        "\n"
+        "build obj/bar/bar.stamp: stamp bar.out\n";
+    EXPECT_EQ(expected_linux, out.str());
+  }
+}
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
new file mode 100644 (file)
index 0000000..e4b0652
--- /dev/null
@@ -0,0 +1,391 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_binary_target_writer.h"
+
+#include <sstream>
+
+#include "base/strings/string_util.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_c_binary_target_writer.h"
+#include "gn/ninja_rust_binary_target_writer.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/ninja_utils.h"
+#include "gn/settings.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+
+namespace {
+
+// Returns the proper escape options for writing compiler and linker flags.
+EscapeOptions GetFlagOptions() {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  return opts;
+}
+
+}  // namespace
+
+NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
+                                                 std::ostream& out)
+    : NinjaTargetWriter(target, out),
+      rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) {}
+
+NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default;
+
+void NinjaBinaryTargetWriter::Run() {
+  if (target_->source_types_used().RustSourceUsed()) {
+    NinjaRustBinaryTargetWriter writer(target_, out_);
+    writer.Run();
+    return;
+  }
+
+  NinjaCBinaryTargetWriter writer(target_, out_);
+  writer.Run();
+}
+
+std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampAndGetDep(
+    size_t num_stamp_uses) const {
+  CHECK(target_->toolchain()) << "Toolchain not set on target "
+                              << target_->label().GetUserVisibleName(true);
+
+  UniqueVector<const SourceFile*> inputs;
+  for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+    for (const auto& input : iter.cur().inputs()) {
+      inputs.push_back(&input);
+    }
+  }
+
+  if (inputs.size() == 0)
+    return std::vector<OutputFile>();  // No inputs
+
+  // If we only have one input, return it directly instead of writing a stamp
+  // file for it.
+  if (inputs.size() == 1) {
+    return std::vector<OutputFile>{
+      OutputFile(settings_->build_settings(), *inputs[0])};
+  }
+
+  std::vector<OutputFile> outs;
+  for (const SourceFile* source : inputs)
+    outs.push_back(OutputFile(settings_->build_settings(), *source));
+
+  // If there are multiple inputs, but the stamp file would be referenced only
+  // once, don't write it but depend on the inputs directly.
+  if (num_stamp_uses == 1u)
+    return outs;
+
+  // Make a stamp file.
+  OutputFile stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  stamp_file.value().append(target_->label().name());
+  stamp_file.value().append(".inputs.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+
+  // File inputs.
+  for (const auto* input : inputs) {
+    out_ << " ";
+    path_output_.WriteFile(out_, *input);
+  }
+
+  out_ << std::endl;
+  return {stamp_file};
+}
+
+void NinjaBinaryTargetWriter::WriteSourceSetStamp(
+    const std::vector<OutputFile>& object_files) {
+  // The stamp rule for source sets is generally not used, since targets that
+  // depend on this will reference the object files directly. However, writing
+  // this rule allows the user to type the name of the target and get a build
+  // which can be convenient for development.
+  ClassifiedDeps classified_deps = GetClassifiedDeps();
+
+  // The classifier should never put extra object files in a source sets: any
+  // source sets that we depend on should appear in our non-linkable deps
+  // instead.
+  DCHECK(classified_deps.extra_object_files.empty());
+
+  std::vector<OutputFile> order_only_deps;
+  for (auto* dep : classified_deps.non_linkable_deps)
+    order_only_deps.push_back(dep->dependency_output_file());
+
+  WriteStampForTarget(object_files, order_only_deps);
+}
+
+NinjaBinaryTargetWriter::ClassifiedDeps
+NinjaBinaryTargetWriter::GetClassifiedDeps() const {
+  ClassifiedDeps classified_deps;
+
+  // Normal public/private deps.
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    ClassifyDependency(pair.ptr, &classified_deps);
+  }
+
+  // Inherited libraries.
+  for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
+    ClassifyDependency(inherited_target, &classified_deps);
+  }
+
+  // Data deps.
+  for (const auto& data_dep_pair : target_->data_deps())
+    classified_deps.non_linkable_deps.push_back(data_dep_pair.ptr);
+
+  return classified_deps;
+}
+
+void NinjaBinaryTargetWriter::ClassifyDependency(
+    const Target* dep,
+    ClassifiedDeps* classified_deps) const {
+  // Only the following types of outputs have libraries linked into them:
+  //  EXECUTABLE
+  //  SHARED_LIBRARY
+  //  _complete_ STATIC_LIBRARY
+  //
+  // Child deps of intermediate static libraries get pushed up the
+  // dependency tree until one of these is reached, and source sets
+  // don't link at all.
+  bool can_link_libs = target_->IsFinal();
+
+  if (can_link_libs && dep->swift_values().builds_module())
+    classified_deps->swiftmodule_deps.push_back(dep);
+
+  if (target_->source_types_used().RustSourceUsed() &&
+      (target_->output_type() == Target::RUST_LIBRARY ||
+       target_->output_type() == Target::STATIC_LIBRARY) &&
+      dep->IsLinkable()) {
+    // Rust libraries and static libraries aren't final, but need to have the
+    // link lines of all transitive deps specified.
+    classified_deps->linkable_deps.push_back(dep);
+  } else if (dep->output_type() == Target::SOURCE_SET ||
+             // If a complete static library depends on an incomplete static
+             // library, manually link in the object files of the dependent
+             // library as if it were a source set. This avoids problems with
+             // braindead tools such as ar which don't properly link dependent
+             // static libraries.
+             (target_->complete_static_lib() &&
+              (dep->output_type() == Target::STATIC_LIBRARY &&
+               !dep->complete_static_lib()))) {
+    // Source sets have their object files linked into final targets
+    // (shared libraries, executables, loadable modules, and complete static
+    // libraries). Intermediate static libraries and other source sets
+    // just forward the dependency, otherwise the files in the source
+    // set can easily get linked more than once which will cause
+    // multiple definition errors.
+    if (can_link_libs)
+      AddSourceSetFiles(dep, &classified_deps->extra_object_files);
+
+    // Add the source set itself as a non-linkable dependency on the current
+    // target. This will make sure that anything the source set's stamp file
+    // depends on (like data deps) are also built before the current target
+    // can be complete. Otherwise, these will be skipped since this target
+    // will depend only on the source set's object files.
+    classified_deps->non_linkable_deps.push_back(dep);
+  } else if (target_->complete_static_lib() && dep->IsFinal()) {
+    classified_deps->non_linkable_deps.push_back(dep);
+  } else if (can_link_libs && dep->IsLinkable()) {
+    classified_deps->linkable_deps.push_back(dep);
+  } else if (dep->output_type() == Target::CREATE_BUNDLE &&
+             dep->bundle_data().is_framework()) {
+    classified_deps->framework_deps.push_back(dep);
+  } else {
+    classified_deps->non_linkable_deps.push_back(dep);
+  }
+}
+
+void NinjaBinaryTargetWriter::AddSourceSetFiles(
+    const Target* source_set,
+    UniqueVector<OutputFile>* obj_files) const {
+  std::vector<OutputFile> tool_outputs;  // Prevent allocation in loop.
+
+  // Compute object files for all sources. Only link the first output from
+  // the tool if there are more than one.
+  for (const auto& source : source_set->sources()) {
+    const char* tool_name = Tool::kToolNone;
+    if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
+      obj_files->push_back(tool_outputs[0]);
+  }
+
+  // Swift files may generate one object file per module or one per source file
+  // depending on how the compiler is invoked (whole module optimization).
+  if (source_set->source_types_used().SwiftSourceUsed()) {
+    const Tool* tool = source_set->toolchain()->GetToolForSourceTypeAsC(
+        SourceFile::SOURCE_SWIFT);
+
+    std::vector<OutputFile> outputs;
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        source_set, tool, tool->outputs(), &outputs);
+
+    for (const OutputFile& output : outputs) {
+      SourceFile output_as_source =
+          output.AsSourceFile(source_set->settings()->build_settings());
+      if (output_as_source.type() == SourceFile::SOURCE_O) {
+        obj_files->push_back(output);
+      }
+    }
+  }
+
+  // Add MSVC precompiled header object files. GCC .gch files are not object
+  // files so they are omitted.
+  if (source_set->config_values().has_precompiled_headers()) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) {
+      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) {
+      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) {
+      const CTool* tool =
+          source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) {
+      const CTool* tool =
+          source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+  }
+}
+
+void NinjaBinaryTargetWriter::WriteCompilerBuildLine(
+    const std::vector<SourceFile>& sources,
+    const std::vector<OutputFile>& extra_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    const char* tool_name,
+    const std::vector<OutputFile>& outputs) {
+  out_ << "build";
+  path_output_.WriteFiles(out_, outputs);
+
+  out_ << ": " << rule_prefix_ << tool_name;
+  path_output_.WriteFiles(out_, sources);
+
+  if (!extra_deps.empty()) {
+    out_ << " |";
+    path_output_.WriteFiles(out_, extra_deps);
+  }
+
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
+  }
+  out_ << std::endl;
+}
+
+void NinjaBinaryTargetWriter::WriteLinkerFlags(
+    std::ostream& out,
+    const Tool* tool,
+    const SourceFile* optional_def_file) {
+  if (tool->AsC()) {
+    // First the ldflags from the target and its config.
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
+                                       GetFlagOptions(), out);
+  }
+
+  // Followed by library search paths that have been recursively pushed
+  // through the dependency tree.
+  const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
+  if (!all_lib_dirs.empty()) {
+    // Since we're passing these on the command line to the linker and not
+    // to Ninja, we need to do shell escaping.
+    PathOutput lib_path_output(path_output_.current_dir(),
+                               settings_->build_settings()->root_path_utf8(),
+                               ESCAPE_NINJA_COMMAND);
+    for (size_t i = 0; i < all_lib_dirs.size(); i++) {
+      out << " " << tool->lib_dir_switch();
+      lib_path_output.WriteDir(out, all_lib_dirs[i],
+                               PathOutput::DIR_NO_LAST_SLASH);
+    }
+  }
+
+  const auto& all_framework_dirs = target_->all_framework_dirs();
+  if (!all_framework_dirs.empty()) {
+    // Since we're passing these on the command line to the linker and not
+    // to Ninja, we need to do shell escaping.
+    PathOutput framework_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    for (size_t i = 0; i < all_framework_dirs.size(); i++) {
+      out << " " << tool->framework_dir_switch();
+      framework_path_output.WriteDir(out, all_framework_dirs[i],
+                                     PathOutput::DIR_NO_LAST_SLASH);
+    }
+  }
+
+  if (optional_def_file) {
+    out_ << " /DEF:";
+    path_output_.WriteFile(out, *optional_def_file);
+  }
+}
+
+void NinjaBinaryTargetWriter::WriteLibs(std::ostream& out, const Tool* tool) {
+  // Libraries that have been recursively pushed through the dependency tree.
+  EscapeOptions lib_escape_opts;
+  lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
+  const OrderedSet<LibFile> all_libs = target_->all_libs();
+  for (size_t i = 0; i < all_libs.size(); i++) {
+    const LibFile& lib_file = all_libs[i];
+    const std::string& lib_value = lib_file.value();
+    if (lib_file.is_source_file()) {
+      out << " " << tool->linker_arg();
+      path_output_.WriteFile(out, lib_file.source_file());
+    } else {
+      out << " " << tool->lib_switch();
+      EscapeStringToStream(out, lib_value, lib_escape_opts);
+    }
+  }
+}
+
+void NinjaBinaryTargetWriter::WriteFrameworks(std::ostream& out,
+                                              const Tool* tool) {
+  // Frameworks that have been recursively pushed through the dependency tree.
+  FrameworksWriter writer(tool->framework_switch());
+  const auto& all_frameworks = target_->all_frameworks();
+  for (size_t i = 0; i < all_frameworks.size(); i++) {
+    writer(all_frameworks[i], out);
+  }
+
+  FrameworksWriter weak_writer(tool->weak_framework_switch());
+  const auto& all_weak_frameworks = target_->all_weak_frameworks();
+  for (size_t i = 0; i < all_weak_frameworks.size(); i++) {
+    weak_writer(all_weak_frameworks[i], out);
+  }
+}
+
+void NinjaBinaryTargetWriter::WriteSwiftModules(
+    std::ostream& out,
+    const Tool* tool,
+    const std::vector<OutputFile>& swiftmodules) {
+  // Since we're passing these on the command line to the linker and not
+  // to Ninja, we need to do shell escaping.
+  PathOutput swiftmodule_path_output(
+      path_output_.current_dir(), settings_->build_settings()->root_path_utf8(),
+      ESCAPE_NINJA_COMMAND);
+
+  for (const OutputFile& swiftmodule : swiftmodules) {
+    out << " " << tool->swiftmodule_switch();
+    swiftmodule_path_output.WriteFile(out, swiftmodule);
+  }
+}
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h
new file mode 100644 (file)
index 0000000..76a8a4e
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/c_tool.h"
+#include "gn/config_values.h"
+#include "gn/ninja_target_writer.h"
+#include "gn/toolchain.h"
+#include "gn/unique_vector.h"
+
+struct EscapeOptions;
+
+// Writes a .ninja file for a binary target type (an executable, a shared
+// library, or a static library).
+class NinjaBinaryTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaBinaryTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaBinaryTargetWriter() override;
+
+  void Run() override;
+
+ protected:
+  // Structure used to return the classified deps from |GetDeps| method.
+  struct ClassifiedDeps {
+    UniqueVector<OutputFile> extra_object_files;
+    UniqueVector<const Target*> linkable_deps;
+    UniqueVector<const Target*> non_linkable_deps;
+    UniqueVector<const Target*> framework_deps;
+    UniqueVector<const Target*> swiftmodule_deps;
+  };
+
+  // Writes to the output stream a stamp rule for inputs, and
+  // returns the file to be appended to source rules that encodes the
+  // implicit dependencies for the current target.
+  // If num_stamp_uses is small, this might return all input dependencies
+  // directly, without writing a stamp file.
+  // If there are no implicit dependencies and no extra target dependencies
+  // are passed in, this returns an empty vector.
+  std::vector<OutputFile> WriteInputsStampAndGetDep(
+      size_t num_stamp_uses) const;
+
+  // Writes the stamp line for a source set. These are not linked.
+  void WriteSourceSetStamp(const std::vector<OutputFile>& object_files);
+
+  // Gets all target dependencies and classifies them, as well as accumulates
+  // object files from source sets we need to link.
+  ClassifiedDeps GetClassifiedDeps() const;
+
+  // Classifies the dependency as linkable or nonlinkable with the current
+  // target, adding it to the appropriate vector of |classified_deps|. If the
+  // dependency is a source set we should link in, the source set's object
+  // files will be appended to |classified_deps.extra_object_files|.
+  void ClassifyDependency(const Target* dep,
+                          ClassifiedDeps* classified_deps) const;
+
+  OutputFile WriteStampAndGetDep(const UniqueVector<const SourceFile*>& files,
+                                 const std::string& stamp_ext) const;
+
+  void WriteCompilerBuildLine(const std::vector<SourceFile>& sources,
+                              const std::vector<OutputFile>& extra_deps,
+                              const std::vector<OutputFile>& order_only_deps,
+                              const char* tool_name,
+                              const std::vector<OutputFile>& outputs);
+
+  void WriteLinkerFlags(std::ostream& out,
+                        const Tool* tool,
+                        const SourceFile* optional_def_file);
+  void WriteLibs(std::ostream& out, const Tool* tool);
+  void WriteFrameworks(std::ostream& out, const Tool* tool);
+  void WriteSwiftModules(std::ostream& out,
+                         const Tool* tool,
+                         const std::vector<OutputFile>& swiftmodules);
+
+  void AddSourceSetFiles(const Target* source_set,
+                         UniqueVector<OutputFile>* obj_files) const;
+
+  // Cached version of the prefix used for rule types for this toolchain.
+  std::string rule_prefix_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NinjaBinaryTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_BINARY_TARGET_WRITER_H_
diff --git a/src/gn/ninja_binary_target_writer_unittest.cc b/src/gn/ninja_binary_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..970aa82
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_binary_target_writer.h"
+
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using NinjaBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaBinaryTargetWriterTest, CSources) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  // Also test object files, which should be just passed through to the
+  // dependents to link.
+  target.sources().push_back(SourceFile("//foo/input3.o"));
+  target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_O);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = bar\n"
+      "\n"
+      "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+      "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaBinaryTargetWriterTest, NoSourcesSourceSet) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = bar\n"
+      "\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaBinaryTargetWriterTest, NoSourcesStaticLib) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.visibility().SetPublic();
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "\n"
+      "build obj/foo/libbar.a: alink\n"
+      "  arflags =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaBinaryTargetWriterTest, Inputs) {
+  Err err;
+  TestWithScope setup;
+
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SOURCE_SET);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//foo/source1.cc"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input1"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input2"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | "
+        "../../foo/input1 ../../foo/input2\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.source1.o\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SOURCE_SET);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//foo/source1.cc"));
+    target.sources().push_back(SourceFile("//foo/source2.cc"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input1"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input2"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.inputs.stamp: stamp "
+        "../../foo/input1 ../../foo/input2\n"
+        "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | "
+        "obj/foo/bar.inputs.stamp\n"
+        "build obj/foo/bar.source2.o: cxx ../../foo/source2.cc | "
+        "obj/foo/bar.inputs.stamp\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.source1.o "
+        "obj/foo/bar.source2.o\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
new file mode 100644 (file)
index 0000000..e7852aa
--- /dev/null
@@ -0,0 +1,642 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_build_writer.h"
+
+#include <stddef.h>
+
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <unordered_set>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/err.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file_manager.h"
+#include "gn/loader.h"
+#include "gn/ninja_utils.h"
+#include "gn/pool.h"
+#include "gn/scheduler.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+#include "util/build_config.h"
+#include "util/exe_path.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace {
+
+struct Counts {
+  Counts() : count(0), last_seen(nullptr) {}
+
+  // Number of targets of this type.
+  int count;
+
+  // The last one we encountered.
+  const Target* last_seen;
+};
+
+}  // namespace
+
+base::CommandLine GetSelfInvocationCommandLine(
+    const BuildSettings* build_settings) {
+  const base::FilePath build_path =
+      build_settings->build_dir().Resolve(build_settings->root_path());
+
+  base::FilePath exe_path = GetExePath();
+  if (build_path.IsAbsolute())
+    exe_path = MakeAbsoluteFilePathRelativeIfPossible(build_path, exe_path);
+
+  base::CommandLine cmdline(exe_path.NormalizePathSeparatorsTo('/'));
+
+  // Use "." for the directory to generate. When Ninja runs the command it
+  // will have the build directory as the current one. Coding it explicitly
+  // will cause everything to get confused if the user renames the directory.
+  cmdline.AppendArg("gen");
+  cmdline.AppendArg(".");
+
+  base::FilePath root_path = build_settings->root_path();
+  if (build_path.IsAbsolute())
+    root_path = MakeAbsoluteFilePathRelativeIfPossible(build_path, root_path);
+
+  cmdline.AppendSwitchPath(std::string("--") + switches::kRoot,
+                           root_path.NormalizePathSeparatorsTo('/'));
+  // Successful automatic invocations shouldn't print output.
+  cmdline.AppendSwitch(std::string("-") + switches::kQuiet);
+
+  EscapeOptions escape_shell;
+  escape_shell.mode = ESCAPE_NINJA_COMMAND;
+#if defined(OS_WIN)
+  // The command line code quoting varies by platform. We have one string,
+  // possibly with spaces, that we want to quote. The Windows command line
+  // quotes again, so we don't want quoting. The Posix one doesn't.
+  escape_shell.inhibit_quoting = true;
+#endif
+
+  // If both --root and --dotfile are passed, make sure the --dotfile is
+  // made relative to the build dir here.
+  base::FilePath dotfile_path = build_settings->dotfile_name();
+  if (!dotfile_path.empty()) {
+    if (build_path.IsAbsolute()) {
+      dotfile_path =
+          MakeAbsoluteFilePathRelativeIfPossible(build_path, dotfile_path);
+    }
+    cmdline.AppendSwitchPath(std::string("--") + switches::kDotfile,
+                             dotfile_path.NormalizePathSeparatorsTo('/'));
+  }
+
+  const base::CommandLine& our_cmdline =
+      *base::CommandLine::ForCurrentProcess();
+  const base::CommandLine::SwitchMap& switches = our_cmdline.GetSwitches();
+  for (base::CommandLine::SwitchMap::const_iterator i = switches.begin();
+       i != switches.end(); ++i) {
+    // Only write arguments we haven't already written. Always skip "args"
+    // since those will have been written to the file and will be used
+    // implicitly in the future. Keeping --args would mean changes to the file
+    // would be ignored.
+    if (i->first != switches::kQuiet && i->first != switches::kRoot &&
+        i->first != switches::kDotfile && i->first != switches::kArgs) {
+      std::string escaped_value =
+          EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr);
+      cmdline.AppendSwitchASCII(i->first, escaped_value);
+    }
+  }
+
+  // Add the regeneration switch if not already present. This is so that when
+  // the regeneration is invoked by ninja, the gen command is aware that it is a
+  // regeneration invocation and not an user invocation. This allows the gen
+  // command to elide ninja post processing steps that ninja will perform
+  // itself.
+  if (!cmdline.HasSwitch(switches::kRegeneration)) {
+    cmdline.AppendSwitch(switches::kRegeneration);
+  }
+
+  return cmdline;
+}
+
+namespace {
+
+std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
+  base::CommandLine cmdline = GetSelfInvocationCommandLine(build_settings);
+#if defined(OS_WIN)
+  return base::UTF16ToUTF8(cmdline.GetCommandLineString());
+#else
+  return cmdline.GetCommandLineString();
+#endif
+}
+
+// Given an output that appears more than once, generates an error message
+// that describes the problem and which targets generate it.
+Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
+                            const OutputFile& bad_output) {
+  std::vector<const Target*> matches;
+  for (const Target* target : all_targets) {
+    for (const auto& output : target->computed_outputs()) {
+      if (output == bad_output) {
+        matches.push_back(target);
+        break;
+      }
+    }
+  }
+
+  // There should always be at least two targets generating this file for this
+  // function to be called in the first place.
+  DCHECK(matches.size() >= 2);
+  std::string matches_string;
+  for (const Target* target : matches)
+    matches_string += "  " + target->label().GetUserVisibleName(false) + "\n";
+
+  Err result(matches[0]->defined_from(), "Duplicate output file.",
+             "Two or more targets generate the same output:\n  " +
+                 bad_output.value() +
+                 "\n\n"
+                 "This is can often be fixed by changing one of the target "
+                 "names, or by \n"
+                 "setting an output_name on one of them.\n"
+                 "\nCollisions:\n" +
+                 matches_string);
+  for (size_t i = 1; i < matches.size(); i++)
+    result.AppendSubErr(Err(matches[i]->defined_from(), "Collision."));
+  return result;
+}
+
+// Given two toolchains with the same name, generates an error message
+// that describes the problem.
+Err GetDuplicateToolchainError(const SourceFile& source_file,
+                               const Toolchain* previous_toolchain,
+                               const Toolchain* toolchain) {
+  Err result(
+      toolchain->defined_from(), "Duplicate toolchain.",
+      "Two or more toolchains write to the same directory:\n  " +
+          source_file.GetDir().value() +
+          "\n\n"
+          "This can be fixed by making sure that distinct toolchains have\n"
+          "distinct names.\n");
+  result.AppendSubErr(
+      Err(previous_toolchain->defined_from(), "Previous toolchain."));
+  return result;
+}
+
+}  // namespace
+
+NinjaBuildWriter::NinjaBuildWriter(
+    const BuildSettings* build_settings,
+    const std::unordered_map<const Settings*, const Toolchain*>&
+        used_toolchains,
+    const std::vector<const Target*>& all_targets,
+    const Toolchain* default_toolchain,
+    const std::vector<const Target*>& default_toolchain_targets,
+    std::ostream& out,
+    std::ostream& dep_out)
+    : build_settings_(build_settings),
+      used_toolchains_(used_toolchains),
+      all_targets_(all_targets),
+      default_toolchain_(default_toolchain),
+      default_toolchain_targets_(default_toolchain_targets),
+      out_(out),
+      dep_out_(dep_out),
+      path_output_(build_settings->build_dir(),
+                   build_settings->root_path_utf8(),
+                   ESCAPE_NINJA) {}
+
+NinjaBuildWriter::~NinjaBuildWriter() = default;
+
+bool NinjaBuildWriter::Run(Err* err) {
+  WriteNinjaRules();
+  WriteAllPools();
+  return WriteSubninjas(err) && WritePhonyAndAllRules(err);
+}
+
+// static
+bool NinjaBuildWriter::RunAndWriteFile(const BuildSettings* build_settings,
+                                       const Builder& builder,
+                                       Err* err) {
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
+
+  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+  std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
+
+  // Find the default toolchain info.
+  Label default_toolchain_label = builder.loader()->GetDefaultToolchain();
+  const Settings* default_toolchain_settings =
+      builder.loader()->GetToolchainSettings(default_toolchain_label);
+  const Toolchain* default_toolchain =
+      builder.GetToolchain(default_toolchain_label);
+
+  // Most targets will be in the default toolchain. Add it at the beginning and
+  // skip adding it to the list every time in the loop.
+  used_toolchains[default_toolchain_settings] = default_toolchain;
+
+  std::vector<const Target*> default_toolchain_targets;
+  default_toolchain_targets.reserve(all_targets.size());
+  for (const Target* target : all_targets) {
+    if (target->settings() == default_toolchain_settings) {
+      default_toolchain_targets.push_back(target);
+      // The default toolchain will already have been added to the used
+      // settings array.
+    } else if (used_toolchains.find(target->settings()) ==
+               used_toolchains.end()) {
+      used_toolchains[target->settings()] =
+          builder.GetToolchain(target->settings()->toolchain_label());
+    }
+  }
+
+  std::stringstream file;
+  std::stringstream depfile;
+  NinjaBuildWriter gen(build_settings, used_toolchains, all_targets,
+                       default_toolchain, default_toolchain_targets, file,
+                       depfile);
+  if (!gen.Run(err))
+    return false;
+
+  // Unconditionally write the build.ninja. Ninja's build-out-of-date checking
+  // will re-run GN when any build input is newer than build.ninja, so any time
+  // the build is updated, build.ninja's timestamp needs to updated also, even
+  // if the contents haven't been changed.
+  base::FilePath ninja_file_name(build_settings->GetFullPath(
+      SourceFile(build_settings->build_dir().value() + "build.ninja")));
+  base::CreateDirectory(ninja_file_name.DirName());
+  std::string ninja_contents = file.str();
+  if (base::WriteFile(ninja_file_name, ninja_contents.data(),
+                      static_cast<int>(ninja_contents.size())) !=
+      static_cast<int>(ninja_contents.size()))
+    return false;
+
+  // Dep file listing build dependencies.
+  base::FilePath dep_file_name(build_settings->GetFullPath(
+      SourceFile(build_settings->build_dir().value() + "build.ninja.d")));
+  std::string dep_contents = depfile.str();
+  if (base::WriteFile(dep_file_name, dep_contents.data(),
+                      static_cast<int>(dep_contents.size())) !=
+      static_cast<int>(dep_contents.size()))
+    return false;
+
+  return true;
+}
+
+void NinjaBuildWriter::WriteNinjaRules() {
+  out_ << "ninja_required_version = "
+       << build_settings_->ninja_required_version().Describe() << "\n\n";
+  out_ << "rule gn\n";
+  out_ << "  command = " << GetSelfInvocationCommand(build_settings_) << "\n";
+  // Putting gn rule to console pool for colorful output on regeneration
+  out_ << "  pool = console\n";
+  out_ << "  description = Regenerating ninja files\n\n";
+
+  // This rule will regenerate the ninja files when any input file has changed.
+  out_ << "build build.ninja: gn\n"
+       << "  generator = 1\n"
+       << "  depfile = build.ninja.d\n";
+
+  // Input build files. These go in the ".d" file. If we write them as
+  // dependencies in the .ninja file itself, ninja will expect the files to
+  // exist and will error if they don't. When files are listed in a depfile,
+  // missing files are ignored.
+  dep_out_ << "build.ninja:";
+
+  // Other files read by the build.
+  std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
+
+  const InputFileManager* input_file_manager =
+      g_scheduler->input_file_manager();
+
+  VectorSetSorter<base::FilePath> sorter(
+      input_file_manager->GetInputFileCount() + other_files.size());
+
+  input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter);
+  sorter.Add(other_files.begin(), other_files.end());
+
+  const base::FilePath build_path =
+      build_settings_->build_dir().Resolve(build_settings_->root_path());
+
+  EscapeOptions depfile_escape;
+  depfile_escape.mode = ESCAPE_DEPFILE;
+  auto item_callback = [this, &depfile_escape,
+                        &build_path](const base::FilePath& input_file) {
+    const base::FilePath file =
+        MakeAbsoluteFilePathRelativeIfPossible(build_path, input_file);
+    dep_out_ << " ";
+    EscapeStringToStream(dep_out_,
+                         FilePathToUTF8(file.NormalizePathSeparatorsTo('/')),
+                         depfile_escape);
+  };
+
+  sorter.IterateOver(item_callback);
+
+  out_ << std::endl;
+}
+
+void NinjaBuildWriter::WriteAllPools() {
+  // Compute the pools referenced by all tools of all used toolchains.
+  std::unordered_set<const Pool*> used_pools;
+  for (const auto& pair : used_toolchains_) {
+    for (const auto& tool : pair.second->tools()) {
+      if (tool.second->pool().ptr)
+        used_pools.insert(tool.second->pool().ptr);
+    }
+  }
+
+  for (const Target* target : all_targets_) {
+    if (target->output_type() == Target::ACTION) {
+      const LabelPtrPair<Pool>& pool = target->action_values().pool();
+      if (pool.ptr)
+        used_pools.insert(pool.ptr);
+    }
+  }
+
+  // Write pools sorted by their name, to make output deterministic.
+  std::vector<const Pool*> sorted_pools(used_pools.begin(), used_pools.end());
+  auto pool_name = [this](const Pool* pool) {
+    return pool->GetNinjaName(default_toolchain_->label());
+  };
+  std::sort(sorted_pools.begin(), sorted_pools.end(),
+            [&pool_name](const Pool* a, const Pool* b) {
+              return pool_name(a) < pool_name(b);
+            });
+  for (const Pool* pool : sorted_pools) {
+    std::string name = pool_name(pool);
+    if (name == "console")
+      continue;
+    out_ << "pool " << name << std::endl
+         << "  depth = " << pool->depth() << std::endl
+         << std::endl;
+  }
+}
+
+bool NinjaBuildWriter::WriteSubninjas(Err* err) {
+  // Write toolchains sorted by their name, to make output deterministic.
+  std::vector<std::pair<const Settings*, const Toolchain*>> sorted_settings(
+      used_toolchains_.begin(), used_toolchains_.end());
+  std::sort(sorted_settings.begin(), sorted_settings.end(),
+            [this](const std::pair<const Settings*, const Toolchain*>& a,
+                   const std::pair<const Settings*, const Toolchain*>& b) {
+              // Always put the default toolchain first.
+              if (b.second == default_toolchain_)
+                return false;
+              if (a.second == default_toolchain_)
+                return true;
+              return GetNinjaFileForToolchain(a.first) <
+                     GetNinjaFileForToolchain(b.first);
+            });
+
+  SourceFile previous_subninja;
+  const Toolchain* previous_toolchain = nullptr;
+
+  for (const auto& pair : sorted_settings) {
+    SourceFile subninja = GetNinjaFileForToolchain(pair.first);
+
+    // Since the toolchains are sorted, comparing to the previous subninja is
+    // enough to find duplicates.
+    if (subninja == previous_subninja) {
+      *err =
+          GetDuplicateToolchainError(subninja, previous_toolchain, pair.second);
+      return false;
+    }
+
+    out_ << "subninja ";
+    path_output_.WriteFile(out_, subninja);
+    out_ << std::endl;
+    previous_subninja = subninja;
+    previous_toolchain = pair.second;
+  }
+  out_ << std::endl;
+  return true;
+}
+
+const char kNinjaRules_Help[] =
+    R"(Ninja build rules
+
+The "all" and "default" rules
+
+  All generated targets (see "gn help execution") will be added to an implicit
+  build rule called "all" so "ninja all" will always compile everything. The
+  default rule will be used by Ninja if no specific target is specified (just
+  typing "ninja"). If there is a target named "default" in the root build file,
+  it will be the default build rule, otherwise the implicit "all" rule will be
+  used.
+
+Phony rules
+
+  GN generates Ninja "phony" rules for targets in the default toolchain.  The
+  phony rules can collide with each other and with the names of generated files
+  so are generated with the following priority:
+
+    1. Actual files generated by the build always take precedence.
+
+    2. Targets in the toplevel //BUILD.gn file.
+
+    3. Targets in toplevel directories matching the names of the directories.
+       So "ninja foo" can be used to compile "//foo:foo". This only applies to
+       the first level of directories since usually these are the most
+       important (so this won't apply to "//foo/bar:bar").
+
+    4. The short names of executables if there is only one executable with that
+       short name. Use "ninja doom_melon" to compile the
+       "//tools/fruit:doom_melon" executable.
+
+    5. The short names of all targets if there is only one target with that
+       short name.
+
+    6. Full label name with no leading slashes. So you can use
+       "ninja tools/fruit:doom_melon" to build "//tools/fruit:doom_melon".
+
+    7. Labels with an implicit name part (when the short names match the
+       directory). So you can use "ninja foo/bar" to compile "//foo/bar:bar".
+
+  These "phony" rules are provided only for running Ninja since this matches
+  people's historical expectations for building. For consistency with the rest
+  of the program, GN introspection commands accept explicit labels.
+
+  To explicitly compile a target in a non-default toolchain, you must give
+  Ninja the exact name of the output file relative to the build directory.
+)";
+
+bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) {
+  // Track rules as we generate them so we don't accidentally write a phony
+  // rule that collides with something else.
+  // GN internally generates an "all" target, so don't duplicate it.
+  std::unordered_set<std::string> written_rules;
+  written_rules.insert("all");
+
+  // Set if we encounter a target named "//:default".
+  const Target* default_target = nullptr;
+
+  // Targets in the root build file.
+  std::vector<const Target*> toplevel_targets;
+
+  // Targets with names matching their toplevel directories. For example
+  // "//foo:foo". Expect this is the naming scheme for "big components."
+  std::vector<const Target*> toplevel_dir_targets;
+
+  // Tracks the number of each target with the given short name, as well
+  // as the short names of executables (which will be a subset of short_names).
+  std::map<std::string, Counts> short_names;
+  std::map<std::string, Counts> exes;
+
+  // ----------------------------------------------------
+  // If you change this algorithm, update the help above!
+  // ----------------------------------------------------
+
+  for (const Target* target : default_toolchain_targets_) {
+    const Label& label = target->label();
+    const std::string& short_name = label.name();
+
+    if (label.dir() == build_settings_->root_target_label().dir() &&
+        short_name == "default")
+      default_target = target;
+
+    // Count the number of targets with the given short name.
+    Counts& short_names_counts = short_names[short_name];
+    short_names_counts.count++;
+    short_names_counts.last_seen = target;
+
+    // Count executables with the given short name.
+    if (target->output_type() == Target::EXECUTABLE) {
+      Counts& exes_counts = exes[short_name];
+      exes_counts.count++;
+      exes_counts.last_seen = target;
+    }
+
+    // Find targets in "important" directories.
+    const std::string& dir_string = label.dir().value();
+    if (dir_string.size() == 2 && dir_string[0] == '/' &&
+        dir_string[1] == '/') {
+      toplevel_targets.push_back(target);
+    } else if (dir_string.size() == label.name().size() + 3 &&  // Size matches.
+               dir_string[0] == '/' &&
+               dir_string[1] == '/' &&  // "//" at beginning.
+               dir_string[dir_string.size() - 1] == '/' &&  // "/" at end.
+               dir_string.compare(2, label.name().size(), label.name()) == 0) {
+      toplevel_dir_targets.push_back(target);
+    }
+
+    // Add the output files from each target to the written rules so that
+    // we don't write phony rules that collide with anything generated by the
+    // build.
+    //
+    // If at this point there is a collision (no phony rules have been
+    // generated yet), two targets make the same output so throw an error.
+    for (const auto& output : target->computed_outputs()) {
+      // Need to normalize because many toolchain outputs will be preceded
+      // with "./".
+      std::string output_string(output.value());
+      NormalizePath(&output_string);
+      if (!written_rules.insert(output_string).second) {
+        *err = GetDuplicateOutputError(default_toolchain_targets_, output);
+        return false;
+      }
+    }
+  }
+
+  // First prefer the short names of toplevel targets.
+  for (const Target* target : toplevel_targets) {
+    if (written_rules.insert(target->label().name()).second)
+      WritePhonyRule(target, target->label().name());
+  }
+
+  // Next prefer short names of toplevel dir targets.
+  for (const Target* target : toplevel_dir_targets) {
+    if (written_rules.insert(target->label().name()).second)
+      WritePhonyRule(target, target->label().name());
+  }
+
+  // Write out the names labels of executables. Many toolchains will produce
+  // executables in the root build directory with no extensions, so the names
+  // will already exist and this will be a no-op.  But on Windows such programs
+  // will have extensions, and executables may override the output directory to
+  // go into some other place.
+  //
+  // Putting this after the "toplevel" rules above also means that you can
+  // steal the short name from an executable by outputting the executable to
+  // a different directory or using a different output name, and writing a
+  // toplevel build rule.
+  for (const auto& pair : exes) {
+    const Counts& counts = pair.second;
+    const std::string& short_name = counts.last_seen->label().name();
+    if (counts.count == 1 && written_rules.insert(short_name).second)
+      WritePhonyRule(counts.last_seen, short_name);
+  }
+
+  // Write short names when those names are unique and not already taken.
+  for (const auto& pair : short_names) {
+    const Counts& counts = pair.second;
+    const std::string& short_name = counts.last_seen->label().name();
+    if (counts.count == 1 && written_rules.insert(short_name).second)
+      WritePhonyRule(counts.last_seen, short_name);
+  }
+
+  // Write the label variants of the target name.
+  for (const Target* target : default_toolchain_targets_) {
+    const Label& label = target->label();
+
+    // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
+    std::string long_name = label.GetUserVisibleName(false);
+    base::TrimString(long_name, "/", &long_name);
+    if (written_rules.insert(long_name).second)
+      WritePhonyRule(target, long_name);
+
+    // Write the directory name with no target name if they match
+    // (e.g. "//foo/bar:bar" -> "foo/bar").
+    if (FindLastDirComponent(label.dir()) == label.name()) {
+      std::string medium_name = DirectoryWithNoLastSlash(label.dir());
+      base::TrimString(medium_name, "/", &medium_name);
+      // That may have generated a name the same as the short name of the
+      // target which we already wrote.
+      if (medium_name != label.name() &&
+          written_rules.insert(medium_name).second)
+        WritePhonyRule(target, medium_name);
+    }
+  }
+
+  // Write the autogenerated "all" rule.
+  if (!default_toolchain_targets_.empty()) {
+    out_ << "\nbuild all: phony";
+
+    EscapeOptions ninja_escape;
+    ninja_escape.mode = ESCAPE_NINJA;
+    for (const Target* target : default_toolchain_targets_) {
+      out_ << " $\n    ";
+      path_output_.WriteFile(out_, target->dependency_output_file());
+    }
+  }
+  out_ << std::endl;
+
+  if (default_target) {
+    // Use the short name when available
+    if (written_rules.find("default") != written_rules.end()) {
+      out_ << "\ndefault default" << std::endl;
+    } else {
+      out_ << "\ndefault ";
+      path_output_.WriteFile(out_, default_target->dependency_output_file());
+      out_ << std::endl;
+    }
+  } else if (!default_toolchain_targets_.empty()) {
+    out_ << "\ndefault all" << std::endl;
+  }
+
+  return true;
+}
+
+void NinjaBuildWriter::WritePhonyRule(const Target* target,
+                                      const std::string& phony_name) {
+  EscapeOptions ninja_escape;
+  ninja_escape.mode = ESCAPE_NINJA;
+
+  // Escape for special chars Ninja will handle.
+  std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
+
+  out_ << "build " << escaped << ": phony ";
+  path_output_.WriteFile(out_, target->dependency_output_file());
+  out_ << std::endl;
+}
diff --git a/src/gn/ninja_build_writer.h b/src/gn/ninja_build_writer.h
new file mode 100644 (file)
index 0000000..9f41a38
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_BUILD_WRITER_H_
+#define TOOLS_GN_NINJA_BUILD_WRITER_H_
+
+#include <iosfwd>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/path_output.h"
+
+class Builder;
+class BuildSettings;
+class Err;
+class Settings;
+class Target;
+class Toolchain;
+
+namespace base {
+class CommandLine;
+}  // base
+
+// Generates the toplevel "build.ninja" file. This references the individual
+// toolchain files and lists all input .gn files as dependencies of the
+// build itself.
+class NinjaBuildWriter {
+ public:
+  NinjaBuildWriter(const BuildSettings* settings,
+                   const std::unordered_map<const Settings*, const Toolchain*>&
+                       used_toolchains,
+                   const std::vector<const Target*>& all_targets,
+                   const Toolchain* default_toolchain,
+                   const std::vector<const Target*>& default_toolchain_targets,
+                   std::ostream& out,
+                   std::ostream& dep_out);
+  ~NinjaBuildWriter();
+
+  // The design of this class is that this static factory function takes the
+  // Builder, extracts the relevant information, and passes it to the class
+  // constructor. The class itself doesn't depend on the Builder at all which
+  // makes testing much easier (tests integrating various functions along with
+  // the Builder get very complicated).
+  static bool RunAndWriteFile(const BuildSettings* settings,
+                              const Builder& builder,
+                              Err* err);
+
+  bool Run(Err* err);
+
+ private:
+  void WriteNinjaRules();
+  void WriteAllPools();
+  bool WriteSubninjas(Err* err);
+  bool WritePhonyAndAllRules(Err* err);
+
+  void WritePhonyRule(const Target* target, const std::string& phony_name);
+
+  const BuildSettings* build_settings_;
+
+  const std::unordered_map<const Settings*, const Toolchain*>& used_toolchains_;
+  const std::vector<const Target*>& all_targets_;
+  const Toolchain* default_toolchain_;
+  const std::vector<const Target*>& default_toolchain_targets_;
+
+  std::ostream& out_;
+  std::ostream& dep_out_;
+  PathOutput path_output_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaBuildWriter);
+};
+
+extern const char kNinjaRules_Help[];
+
+// Exposed for testing.
+base::CommandLine GetSelfInvocationCommandLine(
+    const BuildSettings* build_settings);
+
+#endif  // TOOLS_GN_NINJA_BUILD_WRITER_H_
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc
new file mode 100644 (file)
index 0000000..8bc6839
--- /dev/null
@@ -0,0 +1,255 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "gn/ninja_build_writer.h"
+#include "gn/pool.h"
+#include "gn/scheduler.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using NinjaBuildWriterTest = TestWithScheduler;
+
+class ScopedDotGNFile {
+ public:
+  ScopedDotGNFile(const base::FilePath& path)
+      : path_(path),
+        file_(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE) {
+    EXPECT_TRUE(file_.IsValid());
+  }
+  ~ScopedDotGNFile() {
+    file_.Close();
+    base::DeleteFile(path_, false);
+  }
+
+ private:
+  base::FilePath path_;
+  base::File file_;
+};
+
+TEST_F(NinjaBuildWriterTest, GetSelfInvocationCommandLine) {
+  // TestWithScope sets up a config with a build dir of //out/Debug.
+  TestWithScope setup;
+  base::CommandLine cmd_out(base::CommandLine::NO_PROGRAM);
+
+  // Setup sets the default root dir to ".".
+  base::FilePath root(FILE_PATH_LITERAL("."));
+  base::FilePath root_realpath = base::MakeAbsoluteFilePath(root);
+
+  base::FilePath gn(FILE_PATH_LITERAL("testdot.gn"));
+
+  // The file must exist on disk for MakeAbsoluteFilePath() to work.
+  ScopedDotGNFile dot_gn(gn);
+  base::FilePath gn_realpath = base::MakeAbsoluteFilePath(gn);
+
+  // Without any parameters the self invocation should pass --root=../..
+  // (from //out/Debug to //).
+  setup.build_settings()->SetRootPath(root_realpath);
+  cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_FALSE(cmd_out.HasSwitch(switches::kDotfile));
+
+  // If --root is . and --dotfile is foo/.gn, then --dotfile also needs
+  // to to become ../../foo/.gn.
+  setup.build_settings()->SetRootPath(root_realpath);
+  setup.build_settings()->set_dotfile_name(gn_realpath);
+  cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_EQ("../../testdot.gn",
+            cmd_out.GetSwitchValueASCII(switches::kDotfile));
+}
+
+TEST_F(NinjaBuildWriterTest, TwoTargets) {
+  TestWithScope setup;
+  Err err;
+
+  Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target_foo.set_output_type(Target::ACTION);
+  target_foo.action_values().set_script(SourceFile("//foo/script.py"));
+  target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/out1.out", "//out/Debug/out2.out");
+  target_foo.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_foo.OnResolved(&err));
+
+  Target target_bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+  target_bar.set_output_type(Target::ACTION);
+  target_bar.action_values().set_script(SourceFile("//bar/script.py"));
+  target_bar.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/out3.out", "//out/Debug/out4.out");
+  target_bar.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_bar.OnResolved(&err));
+
+  // Make a secondary toolchain that references two pools.
+  Label other_toolchain_label(SourceDir("//other/"), "toolchain");
+  Toolchain other_toolchain(setup.settings(), other_toolchain_label);
+  TestWithScope::SetupToolchain(&other_toolchain);
+
+  Pool other_regular_pool(
+      setup.settings(),
+      Label(SourceDir("//other/"), "depth_pool", other_toolchain_label.dir(),
+            other_toolchain_label.name()));
+  other_regular_pool.set_depth(42);
+  other_toolchain.GetTool(CTool::kCToolLink)
+      ->set_pool(LabelPtrPair<Pool>(&other_regular_pool));
+
+  // Make another target that uses its own pool
+
+  Pool another_regular_pool(
+      setup.settings(),
+      Label(SourceDir("//another/"), "depth_pool", other_toolchain_label.dir(),
+            other_toolchain_label.name()));
+  another_regular_pool.set_depth(7);
+
+  Target target_baz(setup.settings(), Label(SourceDir("//baz/"), "baz"));
+  target_baz.set_output_type(Target::ACTION);
+  target_baz.action_values().set_script(SourceFile("//baz/script.py"));
+  target_baz.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/out5.out", "//out/Debug/out6.out");
+  target_baz.SetToolchain(&other_toolchain);
+  target_baz.action_values().set_pool(
+      LabelPtrPair<Pool>(&another_regular_pool));
+  ASSERT_TRUE(target_baz.OnResolved(&err));
+
+  // The console pool must be in the default toolchain.
+  Pool console_pool(setup.settings(), Label(SourceDir("//"), "console",
+                                            setup.toolchain()->label().dir(),
+                                            setup.toolchain()->label().name()));
+  console_pool.set_depth(1);
+  other_toolchain.GetTool(GeneralTool::kGeneralToolStamp)
+      ->set_pool(LabelPtrPair<Pool>(&console_pool));
+
+  // Settings to go with the other toolchain.
+  Settings other_settings(setup.build_settings(), "toolchain/");
+  other_settings.set_toolchain_label(other_toolchain_label);
+
+  std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
+  used_toolchains[setup.settings()] = setup.toolchain();
+  used_toolchains[&other_settings] = &other_toolchain;
+
+  std::vector<const Target*> targets = {&target_foo, &target_bar, &target_baz};
+
+  std::ostringstream ninja_out;
+  std::ostringstream depfile_out;
+
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
+                          setup.toolchain(), targets, ninja_out, depfile_out);
+  ASSERT_TRUE(writer.Run(&err));
+
+  const char expected_rule_gn[] = "rule gn\n";
+  const char expected_build_ninja[] =
+      "build build.ninja: gn\n"
+      "  generator = 1\n"
+      "  depfile = build.ninja.d\n";
+  const char expected_other_pool[] =
+      "pool other_toolchain_another_depth_pool\n"
+      "  depth = 7\n"
+      "\n"
+      "pool other_toolchain_other_depth_pool\n"
+      "  depth = 42\n";
+  const char expected_toolchain[] = "subninja toolchain.ninja\n";
+  const char expected_targets[] =
+      "build bar: phony obj/bar/bar.stamp\n"
+      "build baz: phony obj/baz/baz.stamp\n"
+      "build foo$:bar: phony obj/foo/bar.stamp\n"
+      "build bar$:bar: phony obj/bar/bar.stamp\n"
+      "build baz$:baz: phony obj/baz/baz.stamp\n";
+  const char expected_root_target[] =
+      "build all: phony $\n"
+      "    obj/foo/bar.stamp $\n"
+      "    obj/bar/bar.stamp $\n"
+      "    obj/baz/baz.stamp\n";
+  const char expected_default[] = "default all\n";
+  std::string out_str = ninja_out.str();
+#define EXPECT_SNIPPET(expected)                       \
+  EXPECT_NE(std::string::npos, out_str.find(expected)) \
+      << "Expected to find: " << expected << "\n"      \
+      << "Within: " << out_str
+  EXPECT_SNIPPET(expected_rule_gn);
+  EXPECT_SNIPPET(expected_build_ninja);
+  EXPECT_SNIPPET(expected_other_pool);
+  EXPECT_SNIPPET(expected_toolchain);
+  EXPECT_SNIPPET(expected_targets);
+  EXPECT_SNIPPET(expected_root_target);
+  EXPECT_SNIPPET(expected_default);
+#undef EXPECT_SNIPPET
+
+  // A pool definition for ninja's built-in console pool must not be written.
+  EXPECT_EQ(std::string::npos, out_str.find("pool console"));
+}
+
+TEST_F(NinjaBuildWriterTest, SpaceInDepfile) {
+  TestWithScope setup;
+  Err err;
+
+  // Setup sets the default root dir to ".".
+  base::FilePath root(FILE_PATH_LITERAL("."));
+  base::FilePath root_realpath = base::MakeAbsoluteFilePath(root);
+  setup.build_settings()->SetRootPath(root_realpath);
+
+  // Cannot use MakeAbsoluteFilePath for non-existed paths
+  base::FilePath dependency =
+      root_realpath.Append(FILE_PATH_LITERAL("path with space/BUILD.gn"));
+  g_scheduler->AddGenDependency(dependency);
+
+  std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
+  used_toolchains[setup.settings()] = setup.toolchain();
+  std::vector<const Target*> targets;
+  std::ostringstream ninja_out;
+  std::ostringstream depfile_out;
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
+                          setup.toolchain(), targets, ninja_out, depfile_out);
+  ASSERT_TRUE(writer.Run(&err));
+
+  EXPECT_EQ(depfile_out.str(),
+            "build.ninja: ../../path\\ with\\ space/BUILD.gn");
+}
+
+TEST_F(NinjaBuildWriterTest, DuplicateOutputs) {
+  TestWithScope setup;
+  Err err;
+
+  Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target_foo.set_output_type(Target::ACTION);
+  target_foo.action_values().set_script(SourceFile("//foo/script.py"));
+  target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/out1.out", "//out/Debug/out2.out");
+  target_foo.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_foo.OnResolved(&err));
+
+  Target target_bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+  target_bar.set_output_type(Target::ACTION);
+  target_bar.action_values().set_script(SourceFile("//bar/script.py"));
+  target_bar.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/out3.out", "//out/Debug/out2.out");
+  target_bar.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target_bar.OnResolved(&err));
+
+  std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
+  used_toolchains[setup.settings()] = setup.toolchain();
+  std::vector<const Target*> targets = {&target_foo, &target_bar};
+  std::ostringstream ninja_out;
+  std::ostringstream depfile_out;
+  NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
+                          setup.toolchain(), targets, ninja_out, depfile_out);
+  ASSERT_FALSE(writer.Run(&err));
+
+  const char expected_help_test[] =
+      "Two or more targets generate the same output:\n"
+      "  out2.out\n"
+      "\n"
+      "This is can often be fixed by changing one of the target names, or by \n"
+      "setting an output_name on one of them.\n"
+      "\n"
+      "Collisions:\n"
+      "  //foo:bar\n"
+      "  //bar:bar\n";
+
+  EXPECT_EQ(expected_help_test, err.help_text());
+}
diff --git a/src/gn/ninja_bundle_data_target_writer.cc b/src/gn/ninja_bundle_data_target_writer.cc
new file mode 100644 (file)
index 0000000..0e3bcb0
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_bundle_data_target_writer.h"
+
+#include "gn/output_file.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+
+NinjaBundleDataTargetWriter::NinjaBundleDataTargetWriter(const Target* target,
+                                                         std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaBundleDataTargetWriter::~NinjaBundleDataTargetWriter() = default;
+
+void NinjaBundleDataTargetWriter::Run() {
+  std::vector<OutputFile> output_files;
+  for (const SourceFile& source_file : target_->sources()) {
+    output_files.push_back(
+        OutputFile(settings_->build_settings(), source_file));
+  }
+
+  std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), /*num_stamp_uses=*/1);
+  output_files.insert(output_files.end(), input_deps.begin(), input_deps.end());
+
+  std::vector<OutputFile> order_only_deps;
+  for (const auto& pair : target_->data_deps())
+    order_only_deps.push_back(pair.ptr->dependency_output_file());
+
+  WriteStampForTarget(output_files, order_only_deps);
+}
diff --git a/src/gn/ninja_bundle_data_target_writer.h b/src/gn/ninja_bundle_data_target_writer.h
new file mode 100644 (file)
index 0000000..9b8986d
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a bundle_data target type.
+class NinjaBundleDataTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaBundleDataTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaBundleDataTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NinjaBundleDataTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
diff --git a/src/gn/ninja_bundle_data_target_writer_unittest.cc b/src/gn/ninja_bundle_data_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..06b5eb2
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_bundle_data_target_writer.h"
+
+#include <algorithm>
+#include <sstream>
+
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(NinjaBundleDataTargetWriter, Run) {
+  Err err;
+  TestWithScope setup;
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.colorset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaBundleDataTargetWriter writer(&bundle_data, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/foo/data.stamp: stamp "
+      "../../foo/input1.txt "
+      "../../foo/input2.txt "
+      "../../foo/Foo.xcassets/Contents.json "
+      "../../foo/Foo.xcassets/foo.colorset/Contents.json "
+      "../../foo/Foo.xcassets/foo.imageset/Contents.json "
+      "../../foo/Foo.xcassets/foo.imageset/FooIcon-29.png "
+      "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png "
+      "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
new file mode 100644 (file)
index 0000000..17f9c08
--- /dev/null
@@ -0,0 +1,909 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_c_binary_target_writer.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <cstring>
+#include <set>
+#include <sstream>
+#include <unordered_set>
+
+#include "base/strings/string_util.h"
+#include "gn/c_substitution_type.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/ninja_utils.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+struct ModuleDep {
+  ModuleDep(const SourceFile* modulemap,
+            const std::string& module_name,
+            const OutputFile& pcm,
+            bool is_self)
+      : modulemap(modulemap),
+        module_name(module_name),
+        pcm(pcm),
+        is_self(is_self) {}
+
+  // The input module.modulemap source file.
+  const SourceFile* modulemap;
+
+  // The internal module name, in GN this is the target's label.
+  std::string module_name;
+
+  // The compiled version of the module.
+  OutputFile pcm;
+
+  // Is this the module for the current target.
+  bool is_self;
+};
+
+namespace {
+
+// Returns the proper escape options for writing compiler and linker flags.
+EscapeOptions GetFlagOptions() {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  return opts;
+}
+
+// Returns the language-specific lang recognized by gcc’s -x flag for
+// precompiled header files.
+const char* GetPCHLangForToolType(const char* name) {
+  if (name == CTool::kCToolCc)
+    return "c-header";
+  if (name == CTool::kCToolCxx)
+    return "c++-header";
+  if (name == CTool::kCToolObjC)
+    return "objective-c-header";
+  if (name == CTool::kCToolObjCxx)
+    return "objective-c++-header";
+  NOTREACHED() << "Not a valid PCH tool type: " << name;
+  return "";
+}
+
+const SourceFile* GetModuleMapFromTargetSources(const Target* target) {
+  for (const SourceFile& sf : target->sources()) {
+    if (sf.type() == SourceFile::SOURCE_MODULEMAP) {
+      return &sf;
+    }
+  }
+  return nullptr;
+}
+
+std::vector<ModuleDep> GetModuleDepsInformation(const Target* target) {
+  std::vector<ModuleDep> ret;
+
+  auto add = [&ret](const Target* t, bool is_self) {
+    const SourceFile* modulemap = GetModuleMapFromTargetSources(t);
+    CHECK(modulemap);
+
+    std::string label;
+    CHECK(SubstitutionWriter::GetTargetSubstitution(
+        t, &SubstitutionLabelNoToolchain, &label));
+
+    const char* tool_type;
+    std::vector<OutputFile> modulemap_outputs;
+    CHECK(
+        t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs));
+    // Must be only one .pcm from .modulemap.
+    CHECK(modulemap_outputs.size() == 1u);
+    ret.emplace_back(modulemap, label, modulemap_outputs[0], is_self);
+  };
+
+  if (target->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+    add(target, true);
+  }
+
+  for (const auto& pair: target->GetDeps(Target::DEPS_LINKED)) {
+    // Having a .modulemap source means that the dependency is modularized.
+    if (pair.ptr->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+      add(pair.ptr, false);
+    }
+  }
+
+  return ret;
+}
+
+}  // namespace
+
+NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
+                                                   std::ostream& out)
+    : NinjaBinaryTargetWriter(target, out),
+      tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)) {}
+
+NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
+
+void NinjaCBinaryTargetWriter::Run() {
+  std::vector<ModuleDep> module_dep_info = GetModuleDepsInformation(target_);
+
+  WriteCompilerVars(module_dep_info);
+
+  size_t num_stamp_uses = target_->sources().size();
+
+  std::vector<OutputFile> input_deps = WriteInputsStampAndGetDep(
+      num_stamp_uses);
+
+  // The input dependencies will be an order-only dependency. This will cause
+  // Ninja to make sure the inputs are up to date before compiling this source,
+  // but changes in the inputs deps won't cause the file to be recompiled.
+  //
+  // This is important to prevent changes in unrelated actions that are
+  // upstream of this target from causing everything to be recompiled.
+  //
+  // Why can we get away with this rather than using implicit deps ("|", which
+  // will force rebuilds when the inputs change)? For source code, the
+  // computed dependencies of all headers will be computed by the compiler,
+  // which will cause source rebuilds if any "real" upstream dependencies
+  // change.
+  //
+  // If a .cc file is generated by an input dependency, Ninja will see the
+  // input to the build rule doesn't exist, and that it is an output from a
+  // previous step, and build the previous step first. This is a "real"
+  // dependency and doesn't need | or || to express.
+  //
+  // The only case where this rule matters is for the first build where no .d
+  // files exist, and Ninja doesn't know what that source file depends on. In
+  // this case it's sufficient to ensure that the upstream dependencies are
+  // built first. This is exactly what Ninja's order-only dependencies
+  // expresses.
+  //
+  // The order only deps are referenced by each source file compile,
+  // but also by PCH compiles.  The latter are annoying to count, so omit
+  // them here.  This means that binary targets with a single source file
+  // that also use PCH files won't have a stamp file even though having
+  // one would make output ninja file size a bit lower. That's ok, binary
+  // targets with a single source are rare.
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+
+  // For GCC builds, the .gch files are not object files, but still need to be
+  // added as explicit dependencies below. The .gch output files are placed in
+  // |pch_other_files|. This is to prevent linking against them.
+  std::vector<OutputFile> pch_obj_files;
+  std::vector<OutputFile> pch_other_files;
+  WritePCHCommands(input_deps, order_only_deps, &pch_obj_files,
+                   &pch_other_files);
+  std::vector<OutputFile>* pch_files =
+      !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
+
+  // Treat all pch output files as explicit dependencies of all
+  // compiles that support them. Some notes:
+  //
+  //  - On Windows, the .pch file is the input to the compile, not the
+  //    precompiled header's corresponding object file that we're using here.
+  //    But Ninja's depslog doesn't support multiple outputs from the
+  //    precompiled header compile step (it outputs both the .pch file and a
+  //    corresponding .obj file). So we consistently list the .obj file and the
+  //    .pch file we really need comes along with it.
+  //
+  //  - GCC .gch files are not object files, therefore they are not added to the
+  //    object file list.
+  std::vector<OutputFile> obj_files;
+  std::vector<SourceFile> other_files;
+  if (!target_->source_types_used().SwiftSourceUsed()) {
+    WriteSources(*pch_files, input_deps, order_only_deps, module_dep_info,
+                 &obj_files, &other_files);
+  } else {
+    WriteSwiftSources(input_deps, order_only_deps, &obj_files);
+  }
+
+  // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
+  obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
+  if (!CheckForDuplicateObjectFiles(obj_files))
+    return;
+
+  if (target_->output_type() == Target::SOURCE_SET) {
+    WriteSourceSetStamp(obj_files);
+#ifndef NDEBUG
+    // Verify that the function that separately computes a source set's object
+    // files match the object files just computed.
+    UniqueVector<OutputFile> computed_obj;
+    AddSourceSetFiles(target_, &computed_obj);
+    DCHECK_EQ(obj_files.size(), computed_obj.size());
+    for (const auto& obj : obj_files)
+      DCHECK_NE(static_cast<size_t>(-1), computed_obj.IndexOf(obj));
+#endif
+  } else {
+    WriteLinkerStuff(obj_files, other_files, input_deps);
+  }
+}
+
+void NinjaCBinaryTargetWriter::WriteCompilerVars(
+    const std::vector<ModuleDep>& module_dep_info) {
+  const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
+  // Defines.
+  if (subst.used.count(&CSubstitutionDefines)) {
+    out_ << CSubstitutionDefines.ninja_name << " =";
+    RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
+                                               DefineWriter(), out_);
+    out_ << std::endl;
+  }
+
+  // Framework search path.
+  if (subst.used.count(&CSubstitutionFrameworkDirs)) {
+    const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);
+
+    out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
+    PathOutput framework_dirs_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        target_, &ConfigValues::framework_dirs,
+        FrameworkDirsWriter(framework_dirs_output,
+                            tool->framework_dir_switch()),
+        out_);
+    out_ << std::endl;
+  }
+
+  // Include directories.
+  if (subst.used.count(&CSubstitutionIncludeDirs)) {
+    out_ << CSubstitutionIncludeDirs.ninja_name << " =";
+    PathOutput include_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        target_, &ConfigValues::include_dirs,
+        IncludeWriter(include_path_output), out_);
+    out_ << std::endl;
+  }
+
+  if (!module_dep_info.empty()) {
+    // TODO(scottmg): Currently clang modules only working for C++.
+    if (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+        target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+      WriteModuleDepsSubstitution(&CSubstitutionModuleDeps, module_dep_info,
+                                  true);
+      WriteModuleDepsSubstitution(&CSubstitutionModuleDepsNoSelf,
+                                  module_dep_info, false);
+    }
+  }
+
+  bool has_precompiled_headers =
+      target_->config_values().has_precompiled_headers();
+
+  EscapeOptions opts = GetFlagOptions();
+  if (target_->source_types_used().Get(SourceFile::SOURCE_S) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_ASM)) {
+    WriteOneFlag(target_, &CSubstitutionAsmFlags, false, Tool::kToolNone,
+                 &ConfigValues::asmflags, opts, path_output_, out_);
+  }
+  if (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_M) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_MM) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+    WriteOneFlag(target_, &CSubstitutionCFlags, false, Tool::kToolNone,
+                 &ConfigValues::cflags, opts, path_output_, out_);
+  }
+  if (target_->source_types_used().Get(SourceFile::SOURCE_C)) {
+    WriteOneFlag(target_, &CSubstitutionCFlagsC, has_precompiled_headers,
+                 CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
+                 out_);
+  }
+  if (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+      target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
+    WriteOneFlag(target_, &CSubstitutionCFlagsCc, has_precompiled_headers,
+                 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
+                 out_);
+  }
+  if (target_->source_types_used().Get(SourceFile::SOURCE_M)) {
+    WriteOneFlag(target_, &CSubstitutionCFlagsObjC, has_precompiled_headers,
+                 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
+                 path_output_, out_);
+  }
+  if (target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
+    WriteOneFlag(target_, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
+                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
+                 path_output_, out_);
+  }
+  if (target_->source_types_used().SwiftSourceUsed()) {
+    if (subst.used.count(&CSubstitutionSwiftModuleName)) {
+      out_ << CSubstitutionSwiftModuleName.ninja_name << " = ";
+      EscapeStringToStream(out_, target_->swift_values().module_name(), opts);
+      out_ << std::endl;
+    }
+
+    if (subst.used.count(&CSubstitutionSwiftBridgeHeader)) {
+      out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = ";
+      if (!target_->swift_values().bridge_header().is_null()) {
+        path_output_.WriteFile(out_, target_->swift_values().bridge_header());
+      } else {
+        out_ << R"("")";
+      }
+      out_ << std::endl;
+    }
+
+    if (subst.used.count(&CSubstitutionSwiftModuleDirs)) {
+      // Uniquify the list of swiftmodule dirs (in case multiple swiftmodules
+      // are generated in the same directory).
+      UniqueVector<SourceDir> swiftmodule_dirs;
+      for (const Target* dep : target_->swift_values().modules())
+        swiftmodule_dirs.push_back(dep->swift_values().module_output_dir());
+
+      out_ << CSubstitutionSwiftModuleDirs.ninja_name << " =";
+      PathOutput swiftmodule_path_output(
+          path_output_.current_dir(),
+          settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+      IncludeWriter swiftmodule_path_writer(swiftmodule_path_output);
+      for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) {
+        swiftmodule_path_writer(swiftmodule_dir, out_);
+      }
+      out_ << std::endl;
+    }
+
+    WriteOneFlag(target_, &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
+                 &ConfigValues::swiftflags, opts, path_output_, out_);
+  }
+
+  WriteSharedVars(subst);
+}
+
+void NinjaCBinaryTargetWriter::WriteModuleDepsSubstitution(
+    const Substitution* substitution,
+    const std::vector<ModuleDep>& module_dep_info,
+    bool include_self) {
+  if (target_->toolchain()->substitution_bits().used.count(
+          substitution)) {
+    EscapeOptions options;
+    options.mode = ESCAPE_NINJA_COMMAND;
+
+    out_ << substitution->ninja_name << " = -Xclang ";
+    EscapeStringToStream(out_, "-fmodules-embed-all-files", options);
+
+    for (const auto& module_dep : module_dep_info) {
+      if (!module_dep.is_self || include_self) {
+        out_ << " ";
+        EscapeStringToStream(out_, "-fmodule-file=", options);
+        path_output_.WriteFile(out_, module_dep.pcm);
+      }
+    }
+
+    out_ << std::endl;
+  }
+}
+
+void NinjaCBinaryTargetWriter::WritePCHCommands(
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files,
+    std::vector<OutputFile>* other_files) {
+  if (!target_->config_values().has_precompiled_headers())
+    return;
+
+  const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
+  if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
+      target_->source_types_used().Get(SourceFile::SOURCE_C)) {
+    WritePCHCommand(&CSubstitutionCFlagsC, CTool::kCToolCc,
+                    tool_c->precompiled_header_type(), input_deps,
+                    order_only_deps, object_files, other_files);
+  }
+  const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
+  if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
+      target_->source_types_used().Get(SourceFile::SOURCE_CPP)) {
+    WritePCHCommand(&CSubstitutionCFlagsCc, CTool::kCToolCxx,
+                    tool_cxx->precompiled_header_type(), input_deps,
+                    order_only_deps, object_files, other_files);
+  }
+
+  const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
+  if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
+      target_->source_types_used().Get(SourceFile::SOURCE_M)) {
+    WritePCHCommand(&CSubstitutionCFlagsObjC, CTool::kCToolObjC,
+                    tool_objc->precompiled_header_type(), input_deps,
+                    order_only_deps, object_files, other_files);
+  }
+
+  const CTool* tool_objcxx =
+      target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+  if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
+      target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
+    WritePCHCommand(&CSubstitutionCFlagsObjCc, CTool::kCToolObjCxx,
+                    tool_objcxx->precompiled_header_type(), input_deps,
+                    order_only_deps, object_files, other_files);
+  }
+}
+
+void NinjaCBinaryTargetWriter::WritePCHCommand(
+    const Substitution* flag_type,
+    const char* tool_name,
+    CTool::PrecompiledHeaderType header_type,
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files,
+    std::vector<OutputFile>* other_files) {
+  switch (header_type) {
+    case CTool::PCH_MSVC:
+      WriteWindowsPCHCommand(flag_type, tool_name, input_deps, order_only_deps,
+                             object_files);
+      break;
+    case CTool::PCH_GCC:
+      WriteGCCPCHCommand(flag_type, tool_name, input_deps, order_only_deps,
+                         other_files);
+      break;
+    case CTool::PCH_NONE:
+      NOTREACHED() << "Cannot write a PCH command with no PCH header type";
+      break;
+  }
+}
+
+void NinjaCBinaryTargetWriter::WriteGCCPCHCommand(
+    const Substitution* flag_type,
+    const char* tool_name,
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* gch_files) {
+  // Compute the pch output file (it will be language-specific).
+  std::vector<OutputFile> outputs;
+  GetPCHOutputFiles(target_, tool_name, &outputs);
+  if (outputs.empty())
+    return;
+
+  gch_files->insert(gch_files->end(), outputs.begin(), outputs.end());
+
+  std::vector<OutputFile> extra_deps;
+  std::copy(input_deps.begin(), input_deps.end(),
+            std::back_inserter(extra_deps));
+
+  // Build line to compile the file.
+  WriteCompilerBuildLine({target_->config_values().precompiled_source()},
+                         extra_deps, order_only_deps, tool_name, outputs);
+
+  // This build line needs a custom language-specific flags value. Rule-specific
+  // variables are just indented underneath the rule line.
+  out_ << "  " << flag_type->ninja_name << " =";
+
+  // Each substitution flag is overwritten in the target rule to replace the
+  // implicitly generated -include flag with the -x <header lang> flag required
+  // for .gch targets.
+  EscapeOptions opts = GetFlagOptions();
+  if (tool_name == CTool::kCToolCc) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
+                                         out_);
+  } else if (tool_name == CTool::kCToolCxx) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
+                                         opts, out_);
+  } else if (tool_name == CTool::kCToolObjC) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
+                                         opts, out_);
+  } else if (tool_name == CTool::kCToolObjCxx) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
+                                         opts, out_);
+  }
+
+  // Append the command to specify the language of the .gch file.
+  out_ << " -x " << GetPCHLangForToolType(tool_name);
+
+  // Write two blank lines to help separate the PCH build lines from the
+  // regular source build lines.
+  out_ << std::endl << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand(
+    const Substitution* flag_type,
+    const char* tool_name,
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files) {
+  // Compute the pch output file (it will be language-specific).
+  std::vector<OutputFile> outputs;
+  GetPCHOutputFiles(target_, tool_name, &outputs);
+  if (outputs.empty())
+    return;
+
+  object_files->insert(object_files->end(), outputs.begin(), outputs.end());
+
+  std::vector<OutputFile> extra_deps;
+  std::copy(input_deps.begin(), input_deps.end(),
+            std::back_inserter(extra_deps));
+
+  // Build line to compile the file.
+  WriteCompilerBuildLine({target_->config_values().precompiled_source()},
+                         extra_deps, order_only_deps, tool_name, outputs);
+
+  // This build line needs a custom language-specific flags value. Rule-specific
+  // variables are just indented underneath the rule line.
+  out_ << "  " << flag_type->ninja_name << " =";
+
+  // Append the command to generate the .pch file.
+  // This adds the value to the existing flag instead of overwriting it.
+  out_ << " ${" << flag_type->ninja_name << "}";
+  out_ << " /Yc" << target_->config_values().precompiled_header();
+
+  // Write two blank lines to help separate the PCH build lines from the
+  // regular source build lines.
+  out_ << std::endl << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSources(
+    const std::vector<OutputFile>& pch_deps,
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    const std::vector<ModuleDep>& module_dep_info,
+    std::vector<OutputFile>* object_files,
+    std::vector<SourceFile>* other_files) {
+  DCHECK(!target_->source_types_used().SwiftSourceUsed());
+  object_files->reserve(object_files->size() + target_->sources().size());
+
+  std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop.
+  std::vector<OutputFile> deps;
+  for (const auto& source : target_->sources()) {
+    DCHECK_NE(source.type(), SourceFile::SOURCE_SWIFT);
+
+    // Clear the vector but maintain the max capacity to prevent reallocations.
+    deps.resize(0);
+    const char* tool_name = Tool::kToolNone;
+    if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
+      if (source.type() == SourceFile::SOURCE_DEF)
+        other_files->push_back(source);
+      continue;  // No output for this source.
+    }
+
+    std::copy(input_deps.begin(), input_deps.end(), std::back_inserter(deps));
+
+    if (tool_name != Tool::kToolNone) {
+      // Only include PCH deps that correspond to the tool type, for instance,
+      // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep
+      // for the output of a C tool type.
+      //
+      // This makes the assumption that pch_deps only contains pch output files
+      // with the naming scheme specified in GetWindowsPCHObjectExtension or
+      // GetGCCPCHOutputExtension.
+      const CTool* tool = target_->toolchain()->GetToolAsC(tool_name);
+      if (tool->precompiled_header_type() != CTool::PCH_NONE) {
+        for (const auto& dep : pch_deps) {
+          const std::string& output_value = dep.value();
+          size_t extension_offset = FindExtensionOffset(output_value);
+          if (extension_offset == std::string::npos)
+            continue;
+          std::string output_extension;
+          if (tool->precompiled_header_type() == CTool::PCH_MSVC) {
+            output_extension = GetWindowsPCHObjectExtension(
+                tool_name, output_value.substr(extension_offset - 1));
+          } else if (tool->precompiled_header_type() == CTool::PCH_GCC) {
+            output_extension = GetGCCPCHOutputExtension(tool_name);
+          }
+          if (output_value.compare(
+                  output_value.size() - output_extension.size(),
+                  output_extension.size(), output_extension) == 0) {
+            deps.push_back(dep);
+          }
+        }
+      }
+
+      for (const auto& module_dep : module_dep_info) {
+        if (tool_outputs[0] != module_dep.pcm)
+          deps.push_back(module_dep.pcm);
+      }
+
+      WriteCompilerBuildLine({source}, deps, order_only_deps, tool_name,
+                             tool_outputs);
+    }
+
+    // It's theoretically possible for a compiler to produce more than one
+    // output, but we'll only link to the first output.
+    if (source.type() != SourceFile::SOURCE_MODULEMAP) {
+      object_files->push_back(tool_outputs[0]);
+    }
+  }
+
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSwiftSources(
+    const std::vector<OutputFile>& input_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files) {
+  DCHECK(target_->source_types_used().SwiftSourceUsed());
+  object_files->reserve(object_files->size() + target_->sources().size());
+
+  // If the target contains .swift source files, they needs to be compiled as
+  // a single unit but still can produce more than one object file (if the
+  // whole module optimization is disabled).
+  if (target_->source_types_used().SwiftSourceUsed()) {
+    const Tool* tool =
+        target_->toolchain()->GetToolForSourceType(SourceFile::SOURCE_SWIFT);
+
+    const OutputFile swiftmodule_output_file =
+        target_->swift_values().module_output_file();
+
+    std::vector<OutputFile> additional_outputs;
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        target_, tool, tool->outputs(), &additional_outputs);
+
+    additional_outputs.erase(
+        std::remove(additional_outputs.begin(), additional_outputs.end(),
+                    swiftmodule_output_file),
+        additional_outputs.end());
+
+    for (const OutputFile& output : additional_outputs) {
+      const SourceFile output_as_source =
+          output.AsSourceFile(target_->settings()->build_settings());
+
+      if (output_as_source.type() == SourceFile::SOURCE_O) {
+        object_files->push_back(output);
+      }
+    }
+
+    const SubstitutionList& partial_outputs_subst = tool->partial_outputs();
+    if (!partial_outputs_subst.list().empty()) {
+      // Avoid re-allocation during loop.
+      std::vector<OutputFile> partial_outputs;
+      for (const auto& source : target_->sources()) {
+        if (source.type() != SourceFile::SOURCE_SWIFT)
+          continue;
+
+        partial_outputs.resize(0);
+        SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+            target_, source, partial_outputs_subst, &partial_outputs);
+
+        for (const OutputFile& output : partial_outputs) {
+          additional_outputs.push_back(output);
+          SourceFile output_as_source =
+              output.AsSourceFile(target_->settings()->build_settings());
+          if (output_as_source.type() == SourceFile::SOURCE_O) {
+            object_files->push_back(output);
+          }
+        }
+      }
+    }
+
+    UniqueVector<OutputFile> swift_order_only_deps;
+    swift_order_only_deps.reserve(order_only_deps.size());
+    swift_order_only_deps.Append(order_only_deps.begin(),
+                                 order_only_deps.end());
+
+    for (const Target* swiftmodule : target_->swift_values().modules())
+      swift_order_only_deps.push_back(swiftmodule->dependency_output_file());
+
+    WriteCompilerBuildLine(target_->sources(), input_deps,
+                           swift_order_only_deps.vector(), tool->name(),
+                           {swiftmodule_output_file});
+
+    if (!additional_outputs.empty()) {
+      out_ << std::endl;
+      WriteCompilerBuildLine(
+          {swiftmodule_output_file.AsSourceFile(settings_->build_settings())},
+          input_deps, swift_order_only_deps.vector(),
+          GeneralTool::kGeneralToolStamp, additional_outputs);
+    }
+  }
+
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteLinkerStuff(
+    const std::vector<OutputFile>& object_files,
+    const std::vector<SourceFile>& other_files,
+    const std::vector<OutputFile>& input_deps) {
+  std::vector<OutputFile> output_files;
+  SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+      target_, tool_, tool_->outputs(), &output_files);
+
+  out_ << "build";
+  path_output_.WriteFiles(out_, output_files);
+
+  out_ << ": " << rule_prefix_
+       << Tool::GetToolTypeForTargetFinalOutput(target_);
+
+  ClassifiedDeps classified_deps = GetClassifiedDeps();
+
+  // Object files.
+  path_output_.WriteFiles(out_, object_files);
+  path_output_.WriteFiles(out_, classified_deps.extra_object_files);
+
+  // Dependencies.
+  std::vector<OutputFile> implicit_deps;
+  std::vector<OutputFile> solibs;
+  for (const Target* cur : classified_deps.linkable_deps) {
+    // All linkable deps should have a link output file.
+    DCHECK(!cur->link_output_file().value().empty())
+        << "No link output file for "
+        << target_->label().GetUserVisibleName(false);
+
+    if (cur->output_type() == Target::RUST_LIBRARY ||
+        cur->output_type() == Target::RUST_PROC_MACRO)
+      continue;
+
+    if (cur->dependency_output_file().value() !=
+        cur->link_output_file().value()) {
+      // This is a shared library with separate link and deps files. Save for
+      // later.
+      implicit_deps.push_back(cur->dependency_output_file());
+      solibs.push_back(cur->link_output_file());
+    } else {
+      // Normal case, just link to this target.
+      out_ << " ";
+      path_output_.WriteFile(out_, cur->link_output_file());
+    }
+  }
+
+  const SourceFile* optional_def_file = nullptr;
+  if (!other_files.empty()) {
+    for (const SourceFile& src_file : other_files) {
+      if (src_file.type() == SourceFile::SOURCE_DEF) {
+        optional_def_file = &src_file;
+        implicit_deps.push_back(
+            OutputFile(settings_->build_settings(), src_file));
+        break;  // Only one def file is allowed.
+      }
+    }
+  }
+
+  // Libraries specified by paths.
+  const OrderedSet<LibFile>& libs = target_->all_libs();
+  for (size_t i = 0; i < libs.size(); i++) {
+    if (libs[i].is_source_file()) {
+      implicit_deps.push_back(
+          OutputFile(settings_->build_settings(), libs[i].source_file()));
+    }
+  }
+
+  // If any target creates a framework bundle, then treat it as an implicit
+  // dependency via the .stamp file. This is a pessimisation as it is not
+  // always necessary to relink the current target if one of the framework
+  // is regenerated, but it ensure that if one of the framework API changes,
+  // any dependent target will relink it (see crbug.com/1037607).
+  for (const Target* dep : classified_deps.framework_deps) {
+    implicit_deps.push_back(dep->dependency_output_file());
+  }
+
+  // The input dependency is only needed if there are no object files, as the
+  // dependency is normally provided transitively by the source files.
+  std::copy(input_deps.begin(), input_deps.end(),
+            std::back_inserter(implicit_deps));
+
+  // Any C++ target which depends on a Rust .rlib has to depend on its
+  // entire tree of transitive rlibs.
+  std::vector<OutputFile> transitive_rustlibs;
+  if (target_->IsFinal()) {
+    for (const auto* dep :
+         target_->rust_values().transitive_libs().GetOrdered()) {
+      if (dep->output_type() == Target::RUST_LIBRARY) {
+        transitive_rustlibs.push_back(dep->dependency_output_file());
+        implicit_deps.push_back(dep->dependency_output_file());
+      }
+    }
+  }
+
+  // Swift modules from dependencies (and possibly self).
+  std::vector<OutputFile> swiftmodules;
+  if (target_->IsFinal()) {
+    for (const Target* dep : classified_deps.swiftmodule_deps) {
+      swiftmodules.push_back(dep->swift_values().module_output_file());
+      implicit_deps.push_back(dep->swift_values().module_output_file());
+    }
+    if (target_->swift_values().builds_module()) {
+      swiftmodules.push_back(target_->swift_values().module_output_file());
+      implicit_deps.push_back(target_->swift_values().module_output_file());
+    }
+  }
+
+  // Append implicit dependencies collected above.
+  if (!implicit_deps.empty()) {
+    out_ << " |";
+    path_output_.WriteFiles(out_, implicit_deps);
+  }
+
+  // Append data dependencies as order-only dependencies.
+  //
+  // This will include data dependencies and input dependencies (like when
+  // this target depends on an action). Having the data dependencies in this
+  // list ensures that the data is available at runtime when the user builds
+  // this target.
+  //
+  // The action dependencies are not strictly necessary in this case. They
+  // should also have been collected via the input deps stamp that each source
+  // file has for an order-only dependency, and since this target depends on
+  // the sources, there is already an implicit order-only dependency. However,
+  // it's extra work to separate these out and there's no disadvantage to
+  // listing them again.
+  WriteOrderOnlyDependencies(classified_deps.non_linkable_deps);
+
+  // End of the link "build" line.
+  out_ << std::endl;
+
+  // The remaining things go in the inner scope of the link line.
+  if (target_->output_type() == Target::EXECUTABLE ||
+      target_->output_type() == Target::SHARED_LIBRARY ||
+      target_->output_type() == Target::LOADABLE_MODULE) {
+    out_ << "  ldflags =";
+    WriteLinkerFlags(out_, tool_, optional_def_file);
+    out_ << std::endl;
+    out_ << "  libs =";
+    WriteLibs(out_, tool_);
+    out_ << std::endl;
+    out_ << "  frameworks =";
+    WriteFrameworks(out_, tool_);
+    out_ << std::endl;
+    out_ << "  swiftmodules =";
+    WriteSwiftModules(out_, tool_, swiftmodules);
+    out_ << std::endl;
+  } else if (target_->output_type() == Target::STATIC_LIBRARY) {
+    out_ << "  arflags =";
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
+                                         GetFlagOptions(), out_);
+    out_ << std::endl;
+  }
+  WriteOutputSubstitutions();
+  WriteLibsList("solibs", solibs);
+  WriteLibsList("rlibs", transitive_rustlibs);
+}
+
+void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
+  out_ << "  output_extension = "
+       << SubstitutionWriter::GetLinkerSubstitution(
+              target_, tool_, &SubstitutionOutputExtension);
+  out_ << std::endl;
+  out_ << "  output_dir = "
+       << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
+                                                    &SubstitutionOutputDir);
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteLibsList(
+    const std::string& label,
+    const std::vector<OutputFile>& libs) {
+  if (libs.empty())
+    return;
+
+  out_ << "  " << label << " =";
+  path_output_.WriteFiles(out_, libs);
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies(
+    const UniqueVector<const Target*>& non_linkable_deps) {
+  if (!non_linkable_deps.empty()) {
+    out_ << " ||";
+
+    // Non-linkable targets.
+    for (auto* non_linkable_dep : non_linkable_deps) {
+      out_ << " ";
+      path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
+    }
+  }
+}
+
+bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles(
+    const std::vector<OutputFile>& files) const {
+  std::unordered_set<std::string> set;
+  for (const auto& file : files) {
+    if (!set.insert(file.value()).second) {
+      Err err(
+          target_->defined_from(), "Duplicate object file",
+          "The target " + target_->label().GetUserVisibleName(false) +
+              "\ngenerates two object files with the same name:\n  " +
+              file.value() +
+              "\n"
+              "\n"
+              "It could be you accidentally have a file listed twice in the\n"
+              "sources. Or, depending on how your toolchain maps sources to\n"
+              "object files, two source files with the same name in different\n"
+              "directories could map to the same object file.\n"
+              "\n"
+              "In the latter case, either rename one of the files or move one "
+              "of\n"
+              "the sources to a separate source_set to avoid them both being "
+              "in\n"
+              "the same target.");
+      g_scheduler->FailWithError(err);
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h
new file mode 100644 (file)
index 0000000..da75178
--- /dev/null
@@ -0,0 +1,112 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/config_values.h"
+#include "gn/ninja_binary_target_writer.h"
+#include "gn/toolchain.h"
+#include "gn/unique_vector.h"
+
+struct EscapeOptions;
+struct ModuleDep;
+
+// Writes a .ninja file for a binary target type (an executable, a shared
+// library, or a static library).
+class NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter {
+ public:
+  NinjaCBinaryTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaCBinaryTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  using OutputFileSet = std::set<OutputFile>;
+
+  // Writes all flags for the compiler: includes, defines, cflags, etc.
+  void WriteCompilerVars(const std::vector<ModuleDep>& module_dep_info);
+
+  // Write module_deps or module_deps_no_self flags for clang modulemaps.
+  void WriteModuleDepsSubstitution(
+      const Substitution* substitution,
+      const std::vector<ModuleDep>& module_dep_info,
+      bool include_self);
+
+  // Writes build lines required for precompiled headers. Any generated
+  // object files will be appended to the |object_files|. Any generated
+  // non-object files (for instance, .gch files from a GCC toolchain, are
+  // appended to |other_files|).
+  //
+  // input_deps is the stamp file collecting the dependencies required before
+  // compiling this target. It will be empty if there are no input deps.
+  void WritePCHCommands(const std::vector<OutputFile>& input_deps,
+                        const std::vector<OutputFile>& order_only_deps,
+                        std::vector<OutputFile>* object_files,
+                        std::vector<OutputFile>* other_files);
+
+  // Writes a .pch compile build line for a language type.
+  void WritePCHCommand(const Substitution* flag_type,
+                       const char* tool_name,
+                       CTool::PrecompiledHeaderType header_type,
+                       const std::vector<OutputFile>& input_deps,
+                       const std::vector<OutputFile>& order_only_deps,
+                       std::vector<OutputFile>* object_files,
+                       std::vector<OutputFile>* other_files);
+
+  void WriteGCCPCHCommand(const Substitution* flag_type,
+                          const char* tool_name,
+                          const std::vector<OutputFile>& input_deps,
+                          const std::vector<OutputFile>& order_only_deps,
+                          std::vector<OutputFile>* gch_files);
+
+  void WriteWindowsPCHCommand(const Substitution* flag_type,
+                              const char* tool_name,
+                              const std::vector<OutputFile>& input_deps,
+                              const std::vector<OutputFile>& order_only_deps,
+                              std::vector<OutputFile>* object_files);
+
+  // pch_deps are additional dependencies to run before the rule. They are
+  // expected to abide by the naming conventions specified by GetPCHOutputFiles.
+  //
+  // order_only_dep are the dependencies that must be run before doing any
+  // compiles.
+  //
+  // The files produced by the compiler will be added to two output vectors.
+  void WriteSources(const std::vector<OutputFile>& pch_deps,
+                    const std::vector<OutputFile>& input_deps,
+                    const std::vector<OutputFile>& order_only_deps,
+                    const std::vector<ModuleDep>& module_dep_info,
+                    std::vector<OutputFile>* object_files,
+                    std::vector<SourceFile>* other_files);
+  void WriteSwiftSources(const std::vector<OutputFile>& input_deps,
+                         const std::vector<OutputFile>& order_only_deps,
+                         std::vector<OutputFile>* object_files);
+
+  void WriteLinkerStuff(const std::vector<OutputFile>& object_files,
+                        const std::vector<SourceFile>& other_files,
+                        const std::vector<OutputFile>& input_deps);
+  void WriteOutputSubstitutions();
+  void WriteLibsList(const std::string& label,
+                     const std::vector<OutputFile>& libs);
+
+  // Writes the implicit dependencies for the link or stamp line. This is
+  // the "||" and everything following it on the ninja line.
+  //
+  // The order-only dependencies are the non-linkable deps passed in as an
+  // argument, plus the data file dependencies in the target.
+  void WriteOrderOnlyDependencies(
+      const UniqueVector<const Target*>& non_linkable_deps);
+
+  // Checks for duplicates in the given list of output files. If any duplicates
+  // are found, throws an error and return false.
+  bool CheckForDuplicateObjectFiles(const std::vector<OutputFile>& files) const;
+
+  const CTool* tool_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaCBinaryTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..35d9841
--- /dev/null
@@ -0,0 +1,1922 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_c_binary_target_writer.h"
+
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "gn/config.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using NinjaCBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSet) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  // Also test object files, which should be just passed through to the
+  // dependents to link.
+  target.sources().push_back(SourceFile("//foo/input3.o"));
+  target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_O);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Source set itself.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // A shared library that depends on the source set.
+  Target shlib_target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  shlib_target.set_output_type(Target::SHARED_LIBRARY);
+  shlib_target.public_deps().push_back(LabelTargetPair(&target));
+  shlib_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(shlib_target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&shlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libshlib\n"
+        "\n"
+        "\n"
+        // Ordering of the obj files here should come out in the order
+        // specified, with the target's first, followed by the source set's, in
+        // order.
+        "build ./libshlib.so: solink obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+        "|| obj/foo/bar.stamp\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules =\n"
+        "  output_extension = .so\n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // A static library that depends on the source set (should not link it).
+  Target stlib_target(setup.settings(), Label(SourceDir("//foo/"), "stlib"));
+  stlib_target.set_output_type(Target::STATIC_LIBRARY);
+  stlib_target.public_deps().push_back(LabelTargetPair(&target));
+  stlib_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(stlib_target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&stlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libstlib\n"
+        "\n"
+        "\n"
+        // There are no sources so there are no params to alink. (In practice
+        // this will probably fail in the archive tool.)
+        "build obj/foo/libstlib.a: alink || obj/foo/bar.stamp\n"
+        "  arflags =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // Make the static library 'complete', which means it should be linked.
+  stlib_target.set_complete_static_lib(true);
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&stlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libstlib\n"
+        "\n"
+        "\n"
+        // Ordering of the obj files here should come out in the order
+        // specified, with the target's first, followed by the source set's, in
+        // order.
+        "build obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+        "|| obj/foo/bar.stamp\n"
+        "  arflags =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EscapeDefines) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.config_values().defines().push_back("BOOL_DEF");
+  target.config_values().defines().push_back("INT_DEF=123");
+  target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expectedSubstr[] =
+#if defined(OS_WIN)
+      "defines = -DBOOL_DEF -DINT_DEF=123 \"-DSTR_DEF=\\\"ABCD-1\\\"\"";
+#else
+      "defines = -DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\"ABCD-1\\\"";
+#endif
+  std::string out_str = out.str();
+  EXPECT_TRUE(out_str.find(expectedSubstr) != std::string::npos);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, StaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.config_values().arflags().push_back("--asdf");
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+      "\n"
+      "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o\n"
+      "  arflags = --asdf\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, CompleteStaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.config_values().arflags().push_back("--asdf");
+  target.set_complete_static_lib(true);
+
+  TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
+  baz.sources().push_back(SourceFile("//foo/input2.cc"));
+  baz.source_types_used().Set(SourceFile::SOURCE_CPP);
+
+  target.public_deps().push_back(LabelTargetPair(&baz));
+
+  ASSERT_TRUE(target.OnResolved(&err));
+  ASSERT_TRUE(baz.OnResolved(&err));
+
+  // A complete static library that depends on an incomplete static library
+  // should link in the dependent object files as if the dependent target
+  // were a source set.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+        "\n"
+        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+        "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
+        "  arflags = --asdf\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // Make the dependent static library complete.
+  baz.set_complete_static_lib(true);
+
+  // Dependent complete static libraries should not be linked directly.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+        "\n"
+        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+        "|| obj/foo/libbaz.a\n"
+        "  arflags = --asdf\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // An action for our library to depend on.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
+  action.set_output_type(Target::ACTION_FOREACH);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // A shared library w/ the output_extension set to a custom value.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.set_output_extension(std::string("so.6"));
+  target.set_output_dir(SourceDir("//out/Debug/foo/"));
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.public_deps().push_back(LabelTargetPair(&action));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
+      " || obj/foo/action.stamp\n"
+      "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
+      " || obj/foo/action.stamp\n"
+      "\n"
+      "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
+      // The order-only dependency here is stricly unnecessary since the
+      // sources list this as an order-only dep. See discussion in the code
+      // that writes this.
+      "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so.6\n"
+      "  output_dir = foo\n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
+  Err err;
+  TestWithScope setup;
+
+  SourceFile generated_file("//out/Debug/generated.cc");
+
+  // An action does code generation.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
+  action.set_output_type(Target::ACTION);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  action.set_output_dir(SourceDir("//out/Debug/foo/"));
+  action.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/generated.cc");
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // A source set compiling generated code, this target does not publicize any
+  // headers.
+  Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
+  gen_obj.set_output_type(Target::SOURCE_SET);
+  gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_obj.sources().push_back(generated_file);
+  gen_obj.source_types_used().Set(SourceFile::SOURCE_CPP);
+  gen_obj.visibility().SetPublic();
+  gen_obj.private_deps().push_back(LabelTargetPair(&action));
+  gen_obj.set_all_headers_public(false);
+  gen_obj.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_obj.OnResolved(&err));
+
+  std::ostringstream obj_out;
+  NinjaCBinaryTargetWriter obj_writer(&gen_obj, obj_out);
+  obj_writer.Run();
+
+  const char obj_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = gen_obj\n"
+      "\n"
+      "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
+      " || obj/foo/generate.stamp\n"
+      "\n"
+      "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since the
+      // sources list this as an order-only dep.
+      " || obj/foo/generate.stamp\n";
+
+  std::string obj_str = obj_out.str();
+  EXPECT_EQ(obj_expected, obj_str);
+
+  // A shared library depends on gen_obj, having corresponding header for
+  // generated obj.
+  Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
+  gen_lib.set_output_type(Target::SHARED_LIBRARY);
+  gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
+  gen_lib.source_types_used().Set(SourceFile::SOURCE_H);
+  gen_lib.visibility().SetPublic();
+  gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
+  gen_lib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_lib.OnResolved(&err));
+
+  std::ostringstream lib_out;
+  NinjaCBinaryTargetWriter lib_writer(&gen_lib, lib_out);
+  lib_writer.Run();
+
+  const char lib_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libgen_lib\n"
+      "\n"
+      "\n"
+      "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since
+      // obj/out/Debug/gen_obj.generated.o has dependency to
+      // obj/foo/gen_obj.stamp
+      " || obj/foo/gen_obj.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so\n"
+      "  output_dir = foo\n";
+
+  std::string lib_str = lib_out.str();
+  EXPECT_EQ(lib_expected, lib_str);
+
+  // An executable depends on gen_lib.
+  Target executable(setup.settings(),
+                    Label(SourceDir("//foo/"), "final_target"));
+  executable.set_output_type(Target::EXECUTABLE);
+  executable.set_output_dir(SourceDir("//out/Debug/foo/"));
+  executable.sources().push_back(SourceFile("//foo/main.cc"));
+  executable.source_types_used().Set(SourceFile::SOURCE_CPP);
+  executable.private_deps().push_back(LabelTargetPair(&gen_lib));
+  executable.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&executable, final_out);
+  final_writer.Run();
+
+  // There is no order only dependency to action target.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = final_target\n"
+      "\n"
+      "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
+      "\n"
+      "build ./final_target: link obj/foo/final_target.main.o"
+      " ./libgen_lib.so\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = \n"
+      "  output_dir = foo\n";
+
+  std::string final_str = final_out.str();
+  EXPECT_EQ(final_expected, final_str);
+}
+
+// Tests libs are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, LibsAndLibDirs) {
+  Err err;
+  TestWithScope setup;
+
+  // A shared library w/ libs and lib_dirs.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.config_values().libs().push_back(LibFile(SourceFile("//foo/lib1.a")));
+  target.config_values().libs().push_back(LibFile("foo"));
+  target.config_values().lib_dirs().push_back(SourceDir("//foo/bar/"));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "\n"
+      "build ./libshlib.so: solink | ../../foo/lib1.a\n"
+      "  ldflags = -L../../foo/bar\n"
+      "  libs = ../../foo/lib1.a -lfoo\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+// Tests frameworks are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, FrameworksAndFrameworkDirs) {
+  Err err;
+  TestWithScope setup;
+
+  // A config that force linking with the framework.
+  Config framework_config(setup.settings(),
+                          Label(SourceDir("//bar"), "framework_config"));
+  framework_config.visibility().SetPublic();
+  framework_config.own_values().frameworks().push_back("Bar.framework");
+  framework_config.own_values().framework_dirs().push_back(
+      SourceDir("//out/Debug/"));
+  ASSERT_TRUE(framework_config.OnResolved(&err));
+
+  // A target creating a framework bundle.
+  Target framework(setup.settings(), Label(SourceDir("//bar"), "framework"));
+  framework.set_output_type(Target::CREATE_BUNDLE);
+  framework.bundle_data().product_type() = "com.apple.product-type.framework";
+  framework.public_configs().push_back(LabelConfigPair(&framework_config));
+  framework.SetToolchain(setup.toolchain());
+  framework.visibility().SetPublic();
+  ASSERT_TRUE(framework.OnResolved(&err));
+
+  // A shared library w/ libs and lib_dirs.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.config_values().frameworks().push_back("System.framework");
+  target.config_values().weak_frameworks().push_back("Whizbang.framework");
+  target.private_deps().push_back(LabelTargetPair(&framework));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "\n"
+      "build ./libshlib.so: solink | obj/bar/framework.stamp\n"
+      "  ldflags = -F.\n"
+      "  libs =\n"
+      "  frameworks = -framework System -framework Bar "
+      "-weak_framework Whizbang\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EmptyOutputExtension) {
+  Err err;
+  TestWithScope setup;
+
+  // This test is the same as OutputExtensionAndInputDeps, except that we call
+  // set_output_extension("") and ensure that we get an empty one and override
+  // the output prefix so that the name matches the target exactly.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.set_output_prefix_override(true);
+  target.set_output_extension(std::string());
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = shlib\n"
+      "\n"
+      "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
+      "\n"
+      "build ./shlib: solink obj/foo/shlib.input1.o "
+      "obj/foo/shlib.input2.o\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSetDataDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // This target is a data (runtime) dependency of the intermediate target.
+  Target data(setup.settings(), Label(SourceDir("//foo/"), "data_target"));
+  data.set_output_type(Target::EXECUTABLE);
+  data.visibility().SetPublic();
+  data.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(data.OnResolved(&err));
+
+  // Intermediate source set target.
+  Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
+  inter.set_output_type(Target::SOURCE_SET);
+  inter.visibility().SetPublic();
+  inter.data_deps().push_back(LabelTargetPair(&data));
+  inter.SetToolchain(setup.toolchain());
+  inter.sources().push_back(SourceFile("//foo/inter.cc"));
+  inter.source_types_used().Set(SourceFile::SOURCE_CPP);
+  ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
+
+  // Write out the intermediate target.
+  std::ostringstream inter_out;
+  NinjaCBinaryTargetWriter inter_writer(&inter, inter_out);
+  inter_writer.Run();
+
+  // The intermediate source set will be a stamp file that depends on the
+  // object files, and will have an order-only dependency on its data dep and
+  // data file.
+  const char inter_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = inter\n"
+      "\n"
+      "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
+      "\n"
+      "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
+      "./data_target\n";
+  EXPECT_EQ(inter_expected, inter_out.str());
+
+  // Final target.
+  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+  exe.set_output_type(Target::EXECUTABLE);
+  exe.public_deps().push_back(LabelTargetPair(&inter));
+  exe.SetToolchain(setup.toolchain());
+  exe.sources().push_back(SourceFile("//foo/final.cc"));
+  exe.source_types_used().Set(SourceFile::SOURCE_CPP);
+  ASSERT_TRUE(exe.OnResolved(&err));
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+  final_writer.Run();
+
+  // The final output depends on both object files (one from the final target,
+  // one from the source set) and has an order-only dependency on the source
+  // set's stamp file and the final target's data file. The source set stamp
+  // dependency will create an implicit order-only dependency on the data
+  // target.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = exe\n"
+      "\n"
+      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+      "\n"
+      "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
+      "obj/foo/inter.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target shared_lib(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  shared_lib.set_output_type(Target::SHARED_LIBRARY);
+  shared_lib.SetToolchain(setup.toolchain());
+  shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
+  shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
+  shared_lib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  shared_lib.source_types_used().Set(SourceFile::SOURCE_DEF);
+  ASSERT_TRUE(shared_lib.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&shared_lib, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+      "\n"
+      "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
+      "  ldflags = /DEF:../../foo/bar.def\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+  EXPECT_EQ(expected, out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, LoadableModule) {
+  Err err;
+  TestWithScope setup;
+
+  Target loadable_module(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  loadable_module.set_output_type(Target::LOADABLE_MODULE);
+  loadable_module.visibility().SetPublic();
+  loadable_module.SetToolchain(setup.toolchain());
+  loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
+  loadable_module.source_types_used().Set(SourceFile::SOURCE_CPP);
+  ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&loadable_module, out);
+  writer.Run();
+
+  const char loadable_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+      "\n"
+      "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+  EXPECT_EQ(loadable_expected, out.str());
+
+  // Final target.
+  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+  exe.set_output_type(Target::EXECUTABLE);
+  exe.public_deps().push_back(LabelTargetPair(&loadable_module));
+  exe.SetToolchain(setup.toolchain());
+  exe.sources().push_back(SourceFile("//foo/final.cc"));
+  exe.source_types_used().Set(SourceFile::SOURCE_CPP);
+  ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+  final_writer.Run();
+
+  // The final output depends on the loadable module so should have an
+  // order-only dependency on the loadable modules's output file.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = exe\n"
+      "\n"
+      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+      "\n"
+      "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  frameworks =\n"
+      "  swiftmodules =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, WinPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  TestWithScope setup;
+
+  // A precompiled header toolchain.
+  Settings pch_settings(setup.build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cxx));
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_C);
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+    writer.Run();
+
+    const char no_pch_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99\n"
+        "cflags_cc =\n"
+        "target_output_name = no_pch_target\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc\n"
+        "build withpch/obj/foo/no_pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+        "withpch/obj/foo/no_pch_target.input2.o\n";
+    EXPECT_EQ(no_pch_expected, out.str());
+  }
+
+  // This target specifies PCH.
+  {
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_header("build/precompile.h");
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.cc"));
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    pch_target.source_types_used().Set(SourceFile::SOURCE_C);
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&pch_target, out);
+    writer.Run();
+
+    const char pch_win_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        // It should output language-specific pch files.
+        "cflags_c = /Fpwithpch/obj/foo/pch_target_c.pch "
+        "/Yubuild/precompile.h\n"
+        "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch "
+        "/Yubuild/precompile.h\n"
+        "target_output_name = pch_target\n"
+        "\n"
+        // Compile the precompiled source files with /Yc.
+        "build withpch/obj/build/pch_target.precompile.c.o: "
+        "withpch_cc ../../build/precompile.cc\n"
+        "  cflags_c = ${cflags_c} /Ycbuild/precompile.h\n"
+        "\n"
+        "build withpch/obj/build/pch_target.precompile.cc.o: "
+        "withpch_cxx ../../build/precompile.cc\n"
+        "  cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.cc.o\n"
+        "build withpch/obj/foo/pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.c.o\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
+        "withpch/obj/foo/pch_target.input1.o "
+        "withpch/obj/foo/pch_target.input2.o "
+        // The precompiled object files were added to the outputs.
+        "withpch/obj/build/pch_target.precompile.c.o "
+        "withpch/obj/build/pch_target.precompile.cc.o\n";
+    EXPECT_EQ(pch_win_expected, out.str());
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, GCCPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  TestWithScope setup;
+
+  // A precompiled header toolchain.
+  Settings pch_settings(setup.build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cxx));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    no_pch_target.source_types_used().Set(SourceFile::SOURCE_C);
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+    writer.Run();
+
+    const char no_pch_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99\n"
+        "cflags_cc =\n"
+        "target_output_name = no_pch_target\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc\n"
+        "build withpch/obj/foo/no_pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+        "withpch/obj/foo/no_pch_target.input2.o\n";
+    EXPECT_EQ(no_pch_expected, out.str());
+  }
+
+  // This target specifies PCH.
+  {
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.h"));
+    pch_target.config_values().cflags_c().push_back("-std=c99");
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    pch_target.source_types_used().Set(SourceFile::SOURCE_C);
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&pch_target, out);
+    writer.Run();
+
+    const char pch_gcc_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99 "
+        "-include withpch/obj/build/pch_target.precompile.h-c\n"
+        "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n"
+        "target_output_name = pch_target\n"
+        "\n"
+        // Compile the precompiled sources with -x <lang>.
+        "build withpch/obj/build/pch_target.precompile.h-c.gch: "
+        "withpch_cc ../../build/precompile.h\n"
+        "  cflags_c = -std=c99 -x c-header\n"
+        "\n"
+        "build withpch/obj/build/pch_target.precompile.h-cc.gch: "
+        "withpch_cxx ../../build/precompile.h\n"
+        "  cflags_cc = -x c++-header\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.h-cc.gch\n"
+        "build withpch/obj/foo/pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.h-c.gch\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/pch_target.input1.o "
+        "withpch/obj/foo/pch_target.input2.o\n";
+    EXPECT_EQ(pch_gcc_expected, out.str());
+  }
+}
+
+// Should throw an error with the scheduler if a duplicate object file exists.
+// This is dependent on the toolchain's object file mapping.
+TEST_F(NinjaCBinaryTargetWriterTest, DupeObjFileError) {
+  TestWithScope setup;
+  TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
+  target.sources().push_back(SourceFile("//a.cc"));
+  target.sources().push_back(SourceFile("//a.cc"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+
+  EXPECT_FALSE(scheduler().is_failed());
+
+  scheduler().SuppressOutputForTesting(true);
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  scheduler().SuppressOutputForTesting(false);
+
+  // Should have issued an error.
+  EXPECT_TRUE(scheduler().is_failed());
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, InputFiles) {
+  Err err;
+  TestWithScope setup;
+
+  // This target has one input.
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SOURCE_SET);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//foo/input1.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | ../../foo/input.data\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | ../../foo/input.data\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has one input but no source files.
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SHARED_LIBRARY);
+    target.visibility().SetPublic();
+    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "\n"
+        "build ./libbar.so: solink | ../../foo/input.data\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules =\n"
+        "  output_extension = .so\n"
+        "  output_dir = \n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has multiple inputs.
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SOURCE_SET);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//foo/input1.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.inputs.stamp: stamp"
+        " ../../foo/input1.data ../../foo/input2.data\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has one input itself, one from an immediate config, and one
+  // from a config tacked on to said config.
+  {
+    Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
+    far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
+    ASSERT_TRUE(far_config.OnResolved(&err));
+
+    Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
+    config.visibility().SetPublic();
+    config.own_values().inputs().push_back(SourceFile("//foo/input2.data"));
+    config.configs().push_back(LabelConfigPair(&far_config));
+    ASSERT_TRUE(config.OnResolved(&err));
+
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SOURCE_SET);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//foo/input1.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+    target.configs().push_back(LabelConfigPair(&config));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.inputs.stamp: stamp"
+        " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+}
+
+// Test linking of Rust dependencies into C targets.
+TEST_F(NinjaCBinaryTargetWriterTest, RustDeps) {
+  Err err;
+  TestWithScope setup;
+
+  {
+    Target library_target(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+    library_target.set_output_type(Target::STATIC_LIBRARY);
+    library_target.visibility().SetPublic();
+    SourceFile lib("//foo/lib.rs");
+    library_target.sources().push_back(lib);
+    library_target.source_types_used().Set(SourceFile::SOURCE_RS);
+    library_target.rust_values().set_crate_root(lib);
+    library_target.rust_values().crate_name() = "foo";
+    library_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(library_target.OnResolved(&err));
+
+    Target target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    target.set_output_type(Target::EXECUTABLE);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//bar/bar.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.private_deps().push_back(LabelTargetPair(&library_target));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
+        "\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  {
+    Target rlib_target(setup.settings(), Label(SourceDir("//baz/"), "lib"));
+    rlib_target.set_output_type(Target::RUST_LIBRARY);
+    rlib_target.visibility().SetPublic();
+    SourceFile bazlib("//baz/lib.rs");
+    rlib_target.sources().push_back(bazlib);
+    rlib_target.source_types_used().Set(SourceFile::SOURCE_RS);
+    rlib_target.rust_values().set_crate_root(bazlib);
+    rlib_target.rust_values().crate_name() = "lib";
+    rlib_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(rlib_target.OnResolved(&err));
+
+    Target library_target(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+    library_target.set_output_type(Target::STATIC_LIBRARY);
+    library_target.visibility().SetPublic();
+    SourceFile lib("//foo/lib.rs");
+    library_target.sources().push_back(lib);
+    library_target.source_types_used().Set(SourceFile::SOURCE_RS);
+    library_target.rust_values().set_crate_root(lib);
+    library_target.rust_values().crate_name() = "foo";
+    library_target.public_deps().push_back(LabelTargetPair(&rlib_target));
+    library_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(library_target.OnResolved(&err));
+
+    Target rlib_target2(setup.settings(), Label(SourceDir("//qux/"), "lib2"));
+    rlib_target2.set_output_type(Target::RUST_LIBRARY);
+    rlib_target2.visibility().SetPublic();
+    SourceFile quxlib("//qux/lib.rs");
+    rlib_target2.sources().push_back(quxlib);
+    rlib_target2.source_types_used().Set(SourceFile::SOURCE_RS);
+    rlib_target2.rust_values().set_crate_root(quxlib);
+    rlib_target2.rust_values().crate_name() = "lib2";
+    rlib_target2.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(rlib_target2.OnResolved(&err));
+
+    Target rlib_target3(setup.settings(), Label(SourceDir("//quxqux/"), "lib3"));
+    rlib_target3.set_output_type(Target::RUST_LIBRARY);
+    rlib_target3.visibility().SetPublic();
+    SourceFile quxquxlib("//quxqux/lib.rs");
+    rlib_target3.sources().push_back(quxquxlib);
+    rlib_target3.source_types_used().Set(SourceFile::SOURCE_RS);
+    rlib_target3.rust_values().set_crate_root(quxlib);
+    rlib_target3.rust_values().crate_name() = "lib3";
+    rlib_target3.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(rlib_target3.OnResolved(&err));
+
+    Target procmacro(setup.settings(),
+                     Label(SourceDir("//quuxmacro/"), "procmacro"));
+    procmacro.set_output_type(Target::RUST_PROC_MACRO);
+    procmacro.visibility().SetPublic();
+    SourceFile procmacrolib("//procmacro/lib.rs");
+    procmacro.sources().push_back(procmacrolib);
+    procmacro.source_types_used().Set(SourceFile::SOURCE_RS);
+    procmacro.public_deps().push_back(LabelTargetPair(&rlib_target2));
+    procmacro.public_deps().push_back(LabelTargetPair(&rlib_target3));
+    procmacro.rust_values().set_crate_root(procmacrolib);
+    procmacro.rust_values().crate_name() = "procmacro";
+    procmacro.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(procmacro.OnResolved(&err));
+
+    Target rlib_target4(setup.settings(), Label(SourceDir("//quux/"), "lib4"));
+    rlib_target4.set_output_type(Target::RUST_LIBRARY);
+    rlib_target4.visibility().SetPublic();
+    SourceFile quuxlib("//quux/lib.rs");
+    rlib_target4.sources().push_back(quuxlib);
+    rlib_target4.source_types_used().Set(SourceFile::SOURCE_RS);
+    rlib_target4.public_deps().push_back(LabelTargetPair(&rlib_target2));
+    // Transitive proc macros should not impact C++ targets; we're
+    // adding one to ensure the ninja instructions below are unaffected.
+    rlib_target4.public_deps().push_back(LabelTargetPair(&procmacro));
+    rlib_target4.rust_values().set_crate_root(quuxlib);
+    rlib_target4.rust_values().crate_name() = "lib4";
+    rlib_target4.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(rlib_target4.OnResolved(&err));
+
+    Target target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    target.set_output_type(Target::EXECUTABLE);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//bar/bar.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.private_deps().push_back(LabelTargetPair(&library_target));
+    target.private_deps().push_back(LabelTargetPair(&rlib_target4));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
+        "\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a | "
+        "obj/baz/lib.rlib obj/quux/lib4.rlib obj/qux/lib2.rlib\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules =\n"
+        "  output_extension = \n"
+        "  output_dir = \n"
+        "  rlibs = obj/baz/lib.rlib obj/quux/lib4.rlib obj/qux/lib2.rlib\n";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  {
+    Target procmacro(setup.settings(), Label(SourceDir("//baz/"), "macro"));
+    procmacro.set_output_type(Target::LOADABLE_MODULE);
+    procmacro.visibility().SetPublic();
+    SourceFile bazlib("//baz/lib.rs");
+    procmacro.sources().push_back(bazlib);
+    procmacro.source_types_used().Set(SourceFile::SOURCE_RS);
+    procmacro.rust_values().set_crate_root(bazlib);
+    procmacro.rust_values().crate_name() = "macro";
+    procmacro.rust_values().set_crate_type(RustValues::CRATE_PROC_MACRO);
+    procmacro.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(procmacro.OnResolved(&err));
+
+    Target library_target(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+    library_target.set_output_type(Target::STATIC_LIBRARY);
+    library_target.visibility().SetPublic();
+    SourceFile lib("//foo/lib.rs");
+    library_target.sources().push_back(lib);
+    library_target.source_types_used().Set(SourceFile::SOURCE_RS);
+    library_target.rust_values().set_crate_root(lib);
+    library_target.rust_values().crate_name() = "foo";
+    library_target.public_deps().push_back(LabelTargetPair(&procmacro));
+    library_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(library_target.OnResolved(&err));
+
+    Target target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    target.set_output_type(Target::EXECUTABLE);
+    target.visibility().SetPublic();
+    target.sources().push_back(SourceFile("//bar/bar.cc"));
+    target.source_types_used().Set(SourceFile::SOURCE_CPP);
+    target.private_deps().push_back(LabelTargetPair(&library_target));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
+        "\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, ModuleMapInStaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  std::unique_ptr<Tool> cxx_module_tool =
+      Tool::CreateTool(CTool::kCToolCxxModule);
+  cxx_module_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm"));
+  setup.toolchain()->SetTool(std::move(cxx_module_tool));
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/bar.cc"));
+  target.sources().push_back(SourceFile("//foo/bar.modulemap"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.bar.o: cxx ../../foo/bar.cc | obj/foo/libbar.bar.pcm\n"
+      "build obj/foo/libbar.bar.pcm: cxx_module ../../foo/bar.modulemap\n"
+      "\n"
+      "build obj/foo/libbar.a: alink obj/foo/libbar.bar.o\n"
+      "  arflags =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
+// Test linking of targets containing Swift modules.
+TEST_F(NinjaCBinaryTargetWriterTest, SwiftModule) {
+  Err err;
+  TestWithScope setup;
+
+  // A single Swift module.
+  Target foo_target(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+  foo_target.set_output_type(Target::SOURCE_SET);
+  foo_target.visibility().SetPublic();
+  foo_target.sources().push_back(SourceFile("//foo/file1.swift"));
+  foo_target.sources().push_back(SourceFile("//foo/file2.swift"));
+  foo_target.source_types_used().Set(SourceFile::SOURCE_SWIFT);
+  foo_target.swift_values().module_name() = "Foo";
+  foo_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(foo_target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&foo_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "module_name = Foo\n"
+        "module_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = foo\n"
+        "\n"
+        "build obj/foo/Foo.swiftmodule: swift"
+        " ../../foo/file1.swift ../../foo/file2.swift\n"
+        "\n"
+        "build obj/foo/file1.o obj/foo/file2.o: stamp obj/foo/Foo.swiftmodule\n"
+        "\n"
+        "build obj/foo/foo.stamp: stamp"
+        " obj/foo/file1.o obj/foo/file2.o\n";
+
+    const std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // Swift module_dirs correctly set if dependency between Swift modules.
+  {
+    Target bar_target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    bar_target.set_output_type(Target::SOURCE_SET);
+    bar_target.visibility().SetPublic();
+    bar_target.sources().push_back(SourceFile("//bar/bar.swift"));
+    bar_target.source_types_used().Set(SourceFile::SOURCE_SWIFT);
+    bar_target.swift_values().module_name() = "Bar";
+    bar_target.private_deps().push_back(LabelTargetPair(&foo_target));
+    bar_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(bar_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&bar_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "module_name = Bar\n"
+        "module_dirs = -Iobj/foo\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/bar/Bar.swiftmodule: swift ../../bar/bar.swift"
+        " || obj/foo/foo.stamp\n"
+        "\n"
+        "build obj/bar/bar.o: stamp obj/bar/Bar.swiftmodule"
+        " || obj/foo/foo.stamp\n"
+        "\n"
+        "build obj/bar/bar.stamp: stamp obj/bar/bar.o "
+        "|| obj/foo/foo.stamp\n";
+
+    const std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // Swift module_dirs correctly set if dependency between Swift modules,
+  // even if the dependency is indirect (via public_deps).
+  {
+    Target group(setup.settings(), Label(SourceDir("//bar/"), "group"));
+    group.set_output_type(Target::GROUP);
+    group.visibility().SetPublic();
+    group.public_deps().push_back(LabelTargetPair(&foo_target));
+    group.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(group.OnResolved(&err));
+
+    Target bar_target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    bar_target.set_output_type(Target::SOURCE_SET);
+    bar_target.visibility().SetPublic();
+    bar_target.sources().push_back(SourceFile("//bar/bar.swift"));
+    bar_target.source_types_used().Set(SourceFile::SOURCE_SWIFT);
+    bar_target.swift_values().module_name() = "Bar";
+    bar_target.private_deps().push_back(LabelTargetPair(&group));
+    bar_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(bar_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&bar_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "module_name = Bar\n"
+        "module_dirs = -Iobj/foo\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/bar/Bar.swiftmodule: swift ../../bar/bar.swift"
+        " || obj/foo/foo.stamp\n"
+        "\n"
+        "build obj/bar/bar.o: stamp obj/bar/Bar.swiftmodule"
+        " || obj/foo/foo.stamp\n"
+        "\n"
+        "build obj/bar/bar.stamp: stamp obj/bar/bar.o "
+        "|| obj/bar/group.stamp obj/foo/foo.stamp\n";
+
+    const std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  // C target links with module.
+  {
+    Target bar_target(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+    bar_target.set_output_type(Target::EXECUTABLE);
+    bar_target.visibility().SetPublic();
+    bar_target.private_deps().push_back(LabelTargetPair(&foo_target));
+    bar_target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(bar_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&bar_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = bar\n"
+        "\n"
+        "\n"
+        "build ./bar: link obj/foo/file1.o obj/foo/file2.o "
+        "| obj/foo/Foo.swiftmodule "
+        "|| obj/foo/foo.stamp\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  frameworks =\n"
+        "  swiftmodules = obj/foo/Foo.swiftmodule\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+
+    const std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, DependOnModule) {
+  TestWithScope setup;
+  Err err;
+
+  // There's no cxx_module or flags in the test toolchain, set up a
+  // custom one here.
+  Settings module_settings(setup.build_settings(), "withmodules/");
+  Toolchain module_toolchain(&module_settings,
+                             Label(SourceDir("//toolchain/"), "withmodules"));
+  module_settings.set_toolchain_label(module_toolchain.label());
+  module_settings.set_default_toolchain_label(module_toolchain.label());
+
+  std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{module_deps}} "
+      "{{defines}} {{include_dirs}} -o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  module_toolchain.SetTool(std::move(cxx));
+
+  std::unique_ptr<Tool> cxx_module_tool =
+      Tool::CreateTool(CTool::kCToolCxxModule);
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{module_deps_no_self}} "
+      "{{defines}} {{include_dirs}} -fmodule-name={{label}} -c -x c++ "
+      "-Xclang -emit-module -o {{output}}",
+      cxx_module_tool.get());
+  cxx_module_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm"));
+  module_toolchain.SetTool(std::move(cxx_module_tool));
+
+  std::unique_ptr<Tool> alink = Tool::CreateTool(CTool::kCToolAlink);
+  CTool* alink_tool = alink->AsC();
+  TestWithScope::SetCommandForTool("ar {{output}} {{source}}", alink_tool);
+  alink_tool->set_lib_switch("-l");
+  alink_tool->set_lib_dir_switch("-L");
+  alink_tool->set_output_prefix("lib");
+  alink_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}.a"));
+  module_toolchain.SetTool(std::move(alink));
+
+  std::unique_ptr<Tool> link = Tool::CreateTool(CTool::kCToolLink);
+  CTool* link_tool = link->AsC();
+  TestWithScope::SetCommandForTool(
+      "ld -o {{target_output_name}} {{source}} "
+      "{{ldflags}} {{libs}}",
+      link_tool);
+  link_tool->set_lib_switch("-l");
+  link_tool->set_lib_dir_switch("-L");
+  link_tool->set_outputs(
+      SubstitutionList::MakeForTest("{{root_out_dir}}/{{target_output_name}}"));
+  module_toolchain.SetTool(std::move(link));
+
+  module_toolchain.ToolchainSetupComplete();
+
+  Target target(&module_settings, Label(SourceDir("//blah/"), "a"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//blah/a.modulemap"));
+  target.sources().push_back(SourceFile("//blah/a.cc"));
+  target.sources().push_back(SourceFile("//blah/a.h"));
+  target.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  target.SetToolchain(&module_toolchain);
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // The library first.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] = R"(defines =
+include_dirs =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files
+cflags =
+cflags_cc =
+label = //blah$:a
+root_out_dir = withmodules
+target_out_dir = obj/blah
+target_output_name = liba
+
+build obj/blah/liba.a.pcm: cxx_module ../../blah/a.modulemap
+build obj/blah/liba.a.o: cxx ../../blah/a.cc | obj/blah/liba.a.pcm
+
+build obj/blah/liba.a: alink obj/blah/liba.a.o
+  arflags =
+  output_extension = 
+  output_dir = 
+)";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target target2(&module_settings, Label(SourceDir("//stuff/"), "b"));
+  target2.set_output_type(Target::STATIC_LIBRARY);
+  target2.visibility().SetPublic();
+  target2.sources().push_back(SourceFile("//stuff/b.modulemap"));
+  target2.sources().push_back(SourceFile("//stuff/b.cc"));
+  target2.sources().push_back(SourceFile("//stuff/b.h"));
+  target2.source_types_used().Set(SourceFile::SOURCE_CPP);
+  target2.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  target2.SetToolchain(&module_toolchain);
+  ASSERT_TRUE(target2.OnResolved(&err));
+
+  // A second library to make sure the depender includes both.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target2, out);
+    writer.Run();
+
+    const char expected[] = R"(defines =
+include_dirs =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libb.b.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files
+cflags =
+cflags_cc =
+label = //stuff$:b
+root_out_dir = withmodules
+target_out_dir = obj/stuff
+target_output_name = libb
+
+build obj/stuff/libb.b.pcm: cxx_module ../../stuff/b.modulemap
+build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/stuff/libb.b.pcm
+
+build obj/stuff/libb.a: alink obj/stuff/libb.b.o
+  arflags =
+  output_extension = 
+  output_dir = 
+)";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target target3(&module_settings, Label(SourceDir("//things/"), "c"));
+  target3.set_output_type(Target::STATIC_LIBRARY);
+  target3.visibility().SetPublic();
+  target3.sources().push_back(SourceFile("//stuff/c.modulemap"));
+  target3.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+  target3.private_deps().push_back(LabelTargetPair(&target));
+  target3.SetToolchain(&module_toolchain);
+  ASSERT_TRUE(target3.OnResolved(&err));
+
+  // A third library that depends on one of the previous static libraries, to
+  // check module_deps_no_self.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target3, out);
+    writer.Run();
+
+    const char expected[] = R"(defines =
+include_dirs =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libc.c.pcm -fmodule-file=obj/blah/liba.a.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
+cflags =
+cflags_cc =
+label = //things$:c
+root_out_dir = withmodules
+target_out_dir = obj/things
+target_output_name = libc
+
+build obj/stuff/libc.c.pcm: cxx_module ../../stuff/c.modulemap | obj/blah/liba.a.pcm
+
+build obj/things/libc.a: alink || obj/blah/liba.a
+  arflags =
+  output_extension = 
+  output_dir = 
+)";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target depender(&module_settings, Label(SourceDir("//zap/"), "c"));
+  depender.set_output_type(Target::EXECUTABLE);
+  depender.sources().push_back(SourceFile("//zap/x.cc"));
+  depender.sources().push_back(SourceFile("//zap/y.cc"));
+  depender.source_types_used().Set(SourceFile::SOURCE_CPP);
+  depender.private_deps().push_back(LabelTargetPair(&target));
+  depender.private_deps().push_back(LabelTargetPair(&target2));
+  depender.SetToolchain(&module_toolchain);
+  ASSERT_TRUE(depender.OnResolved(&err));
+
+  // Then the executable that depends on it.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&depender, out);
+    writer.Run();
+
+    const char expected[] = R"(defines =
+include_dirs =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
+cflags =
+cflags_cc =
+label = //zap$:c
+root_out_dir = withmodules
+target_out_dir = obj/zap
+target_output_name = c
+
+build obj/zap/c.x.o: cxx ../../zap/x.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
+build obj/zap/c.y.o: cxx ../../zap/y.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm
+
+build withmodules/c: link obj/zap/c.x.o obj/zap/c.y.o obj/blah/liba.a obj/stuff/libb.a
+  ldflags =
+  libs =
+  frameworks =
+  swiftmodules =
+  output_extension = 
+  output_dir = 
+)";
+
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/ninja_copy_target_writer.cc b/src/gn/ninja_copy_target_writer.cc
new file mode 100644 (file)
index 0000000..9299223
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_copy_target_writer.h"
+
+#include "base/strings/string_util.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_utils.h"
+#include "gn/output_file.h"
+#include "gn/scheduler.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/toolchain.h"
+
+NinjaCopyTargetWriter::NinjaCopyTargetWriter(const Target* target,
+                                             std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaCopyTargetWriter::~NinjaCopyTargetWriter() = default;
+
+void NinjaCopyTargetWriter::Run() {
+  const Tool* copy_tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolCopy);
+  if (!copy_tool) {
+    g_scheduler->FailWithError(Err(
+        nullptr, "Copy tool not defined",
+        "The toolchain " +
+            target_->toolchain()->label().GetUserVisibleName(false) +
+            "\n used by target " + target_->label().GetUserVisibleName(false) +
+            "\n doesn't define a \"copy\" tool."));
+    return;
+  }
+
+  const Tool* stamp_tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolStamp);
+  if (!stamp_tool) {
+    g_scheduler->FailWithError(Err(
+        nullptr, "Copy tool not defined",
+        "The toolchain " +
+            target_->toolchain()->label().GetUserVisibleName(false) +
+            "\n used by target " + target_->label().GetUserVisibleName(false) +
+            "\n doesn't define a \"stamp\" tool."));
+    return;
+  }
+
+  // Figure out the substitutions used by the copy and stamp tools.
+  SubstitutionBits required_bits = copy_tool->substitution_bits();
+  required_bits.MergeFrom(stamp_tool->substitution_bits());
+
+  // General target-related substitutions needed by both tools.
+  WriteSharedVars(required_bits);
+
+  std::vector<OutputFile> output_files;
+  WriteCopyRules(&output_files);
+  out_ << std::endl;
+  WriteStampForTarget(output_files, std::vector<OutputFile>());
+}
+
+void NinjaCopyTargetWriter::WriteCopyRules(
+    std::vector<OutputFile>* output_files) {
+  CHECK(target_->action_values().outputs().list().size() == 1);
+  const SubstitutionList& output_subst_list =
+      target_->action_values().outputs();
+  CHECK_EQ(1u, output_subst_list.list().size())
+      << "Should have one entry exactly.";
+  const SubstitutionPattern& output_subst = output_subst_list.list()[0];
+
+  std::string tool_name = GetNinjaRulePrefixForToolchain(settings_) +
+                          GeneralTool::kGeneralToolCopy;
+
+  size_t num_stamp_uses = target_->sources().size();
+  std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+
+  std::vector<OutputFile> data_outs;
+  for (const auto& dep : target_->data_deps())
+    data_outs.push_back(dep.ptr->dependency_output_file());
+
+  // Note that we don't write implicit deps for copy steps. "copy" only
+  // depends on the output files themselves, rather than having includes
+  // (the possibility of generated #includes is the main reason for implicit
+  // dependencies).
+  //
+  // It would seem that specifying implicit dependencies on the deps of the
+  // copy command would still be harmless. But Chrome implements copy tools
+  // as hard links (much faster) which don't change the timestamp. If the
+  // ninja rule looks like this:
+  //   output: copy input | foo.stamp
+  // The copy will not make a new timestamp on the output file, but the
+  // foo.stamp file generated from a previous step will have a new timestamp.
+  // The copy rule will therefore look out-of-date to Ninja and the rule will
+  // get rebuilt.
+  //
+  // If this copy is copying a generated file, not listing the implicit
+  // dependency will be fine as long as the input to the copy is properly
+  // listed as the output from the step that generated it.
+  //
+  // Moreover, doing this assumes that the copy step is always a simple
+  // locally run command, so there is no need for a toolchain dependency.
+  //
+  // Note that there is the need in some cases for order-only dependencies
+  // where a command might need to make sure something else runs before it runs
+  // to avoid conflicts. This is also needed for data_deps on a copy target.
+  // Such cases should be avoided where possible, but sometimes that's not
+  // possible.
+  for (const auto& input_file : target_->sources()) {
+    OutputFile output_file =
+        SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
+            target_, target_->settings(), output_subst, input_file);
+    output_files->push_back(output_file);
+
+    out_ << "build ";
+    path_output_.WriteFile(out_, output_file);
+    out_ << ": " << tool_name << " ";
+    path_output_.WriteFile(out_, input_file);
+    if (!input_deps.empty() || !data_outs.empty()) {
+      out_ << " ||";
+      path_output_.WriteFiles(out_, input_deps);
+      path_output_.WriteFiles(out_, data_outs);
+    }
+    out_ << std::endl;
+  }
+}
diff --git a/src/gn/ninja_copy_target_writer.h b/src/gn/ninja_copy_target_writer.h
new file mode 100644 (file)
index 0000000..c4919c6
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_COPY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_COPY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a copy target type.
+class NinjaCopyTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaCopyTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaCopyTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  // Writes the rules top copy the file(s), putting the computed output file
+  // name(s) into the given vector.
+  void WriteCopyRules(std::vector<OutputFile>* output_files);
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaCopyTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_COPY_TARGET_WRITER_H_
diff --git a/src/gn/ninja_copy_target_writer_unittest.cc b/src/gn/ninja_copy_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..f641ffa
--- /dev/null
@@ -0,0 +1,126 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <sstream>
+
+#include "gn/ninja_copy_target_writer.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+// Tests multiple files with an output pattern and no toolchain dependency.
+TEST(NinjaCopyTargetWriter, Run) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::COPY_FILES);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.sources().push_back(SourceFile("//foo/input2.txt"));
+
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCopyTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "build input1.out: copy ../../foo/input1.txt\n"
+      "build input2.out: copy ../../foo/input2.txt\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected_linux, out_str);
+}
+
+// Tests a single file with no output pattern.
+TEST(NinjaCopyTargetWriter, ToolchainDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::COPY_FILES);
+
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/output.out");
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCopyTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "build output.out: copy ../../foo/input1.txt\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp output.out\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected_linux, out_str);
+}
+
+TEST(NinjaCopyTargetWriter, OrderOnlyDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::COPY_FILES);
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+  target.config_values().inputs().push_back(SourceFile("//foo/script.py"));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCopyTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "build input1.out: copy ../../foo/input1.txt || ../../foo/script.py\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected_linux, out_str);
+}
+
+TEST(NinjaCopyTargetWriter, DataDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::COPY_FILES);
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+
+  Target data_dep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
+  data_dep.set_output_type(Target::ACTION);
+  data_dep.visibility().SetPublic();
+  data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(data_dep.OnResolved(&err));
+
+  target.data_deps().push_back(LabelTargetPair(&data_dep));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCopyTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "build input1.out: copy ../../foo/input1.txt || obj/foo/datadep.stamp\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected_linux, out_str);
+}
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc
new file mode 100644 (file)
index 0000000..9f000f3
--- /dev/null
@@ -0,0 +1,373 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_create_bundle_target_writer.h"
+
+#include <iterator>
+
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_utils.h"
+#include "gn/output_file.h"
+#include "gn/scheduler.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/toolchain.h"
+
+namespace {
+
+bool TargetRequireAssetCatalogCompilation(const Target* target) {
+  return !target->bundle_data().assets_catalog_sources().empty() ||
+         !target->bundle_data().partial_info_plist().is_null();
+}
+
+void FailWithMissingToolError(const char* tool_name, const Target* target) {
+  g_scheduler->FailWithError(
+      Err(nullptr, std::string(tool_name) + " tool not defined",
+          "The toolchain " +
+              target->toolchain()->label().GetUserVisibleName(false) +
+              "\n"
+              "used by target " +
+              target->label().GetUserVisibleName(false) +
+              "\n"
+              "doesn't define a \"" +
+              tool_name + "\" tool."));
+}
+
+bool EnsureAllToolsAvailable(const Target* target) {
+  const char* kRequiredTools[] = {
+      GeneralTool::kGeneralToolCopyBundleData,
+      GeneralTool::kGeneralToolStamp,
+  };
+
+  for (size_t i = 0; i < std::size(kRequiredTools); ++i) {
+    if (!target->toolchain()->GetTool(kRequiredTools[i])) {
+      FailWithMissingToolError(kRequiredTools[i], target);
+      return false;
+    }
+  }
+
+  // The compile_xcassets tool is only required if the target has asset
+  // catalog resources to compile.
+  if (TargetRequireAssetCatalogCompilation(target)) {
+    if (!target->toolchain()->GetTool(
+            GeneralTool::kGeneralToolCompileXCAssets)) {
+      FailWithMissingToolError(GeneralTool::kGeneralToolCompileXCAssets,
+                               target);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+NinjaCreateBundleTargetWriter::NinjaCreateBundleTargetWriter(
+    const Target* target,
+    std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaCreateBundleTargetWriter::~NinjaCreateBundleTargetWriter() = default;
+
+void NinjaCreateBundleTargetWriter::Run() {
+  if (!EnsureAllToolsAvailable(target_))
+    return;
+
+  // Stamp users are CopyBundleData, CompileAssetsCatalog, CodeSigning and
+  // StampForTarget.
+  size_t num_stamp_uses = 4;
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+
+  std::string code_signing_rule_name = WriteCodeSigningRuleDefinition();
+
+  std::vector<OutputFile> output_files;
+  WriteCopyBundleDataSteps(order_only_deps, &output_files);
+  WriteCompileAssetsCatalogStep(order_only_deps, &output_files);
+  WriteCodeSigningStep(code_signing_rule_name, order_only_deps, &output_files);
+
+  for (const auto& pair : target_->data_deps())
+    order_only_deps.push_back(pair.ptr->dependency_output_file());
+  WriteStampForTarget(output_files, order_only_deps);
+
+  // Write a phony target for the outer bundle directory. This allows other
+  // targets to treat the entire bundle as a single unit, even though it is
+  // a directory, so that it can be depended upon as a discrete build edge.
+  out_ << "build ";
+  path_output_.WriteFile(
+      out_,
+      OutputFile(settings_->build_settings(),
+                 target_->bundle_data().GetBundleRootDirOutput(settings_)));
+  out_ << ": phony " << target_->dependency_output_file().value();
+  out_ << std::endl;
+}
+
+std::string NinjaCreateBundleTargetWriter::WriteCodeSigningRuleDefinition() {
+  if (target_->bundle_data().code_signing_script().is_null())
+    return std::string();
+
+  std::string target_label = target_->label().GetUserVisibleName(true);
+  std::string custom_rule_name(target_label);
+  base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
+  custom_rule_name.append("_code_signing_rule");
+
+  out_ << "rule " << custom_rule_name << std::endl;
+  out_ << "  command = ";
+  path_output_.WriteFile(out_, settings_->build_settings()->python_path());
+  out_ << " ";
+  path_output_.WriteFile(out_, target_->bundle_data().code_signing_script());
+
+  const SubstitutionList& args = target_->bundle_data().code_signing_args();
+  EscapeOptions args_escape_options;
+  args_escape_options.mode = ESCAPE_NINJA_COMMAND;
+
+  for (const auto& arg : args.list()) {
+    out_ << " ";
+    SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
+  }
+  out_ << std::endl;
+  out_ << "  description = CODE SIGNING " << target_label << std::endl;
+  out_ << "  restat = 1" << std::endl;
+  out_ << std::endl;
+
+  return custom_rule_name;
+}
+
+void NinjaCreateBundleTargetWriter::WriteCopyBundleDataSteps(
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* output_files) {
+  for (const BundleFileRule& file_rule : target_->bundle_data().file_rules())
+    WriteCopyBundleFileRuleSteps(file_rule, order_only_deps, output_files);
+}
+
+void NinjaCreateBundleTargetWriter::WriteCopyBundleFileRuleSteps(
+    const BundleFileRule& file_rule,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* output_files) {
+  // Note that we don't write implicit deps for copy steps. "copy_bundle_data"
+  // steps as this is most likely implemented using hardlink in the common case.
+  // See NinjaCopyTargetWriter::WriteCopyRules() for a detailed explanation.
+  for (const SourceFile& source_file : file_rule.sources()) {
+    // There is no need to check for errors here as the substitution will have
+    // been performed when computing the list of output of the target during
+    // the Target::OnResolved phase earlier.
+    OutputFile expanded_output_file;
+    file_rule.ApplyPatternToSourceAsOutputFile(
+        settings_, target_, target_->bundle_data(), source_file,
+        &expanded_output_file,
+        /*err=*/nullptr);
+    output_files->push_back(expanded_output_file);
+
+    out_ << "build ";
+    path_output_.WriteFile(out_, expanded_output_file);
+    out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+         << GeneralTool::kGeneralToolCopyBundleData << " ";
+    path_output_.WriteFile(out_, source_file);
+
+    if (!order_only_deps.empty()) {
+      out_ << " ||";
+      path_output_.WriteFiles(out_, order_only_deps);
+    }
+
+    out_ << std::endl;
+  }
+}
+
+void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogStep(
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* output_files) {
+  if (!TargetRequireAssetCatalogCompilation(target_))
+    return;
+
+  OutputFile compiled_catalog;
+  if (!target_->bundle_data().assets_catalog_sources().empty()) {
+    compiled_catalog =
+        OutputFile(settings_->build_settings(),
+                   target_->bundle_data().GetCompiledAssetCatalogPath());
+    output_files->push_back(compiled_catalog);
+  }
+
+  OutputFile partial_info_plist;
+  if (!target_->bundle_data().partial_info_plist().is_null()) {
+    partial_info_plist =
+        OutputFile(settings_->build_settings(),
+                   target_->bundle_data().partial_info_plist());
+
+    output_files->push_back(partial_info_plist);
+  }
+
+  // If there are no asset catalog to compile but the "partial_info_plist" is
+  // non-empty, then add a target to generate an empty file (to avoid breaking
+  // code that depends on this file existence).
+  if (target_->bundle_data().assets_catalog_sources().empty()) {
+    DCHECK(!target_->bundle_data().partial_info_plist().is_null());
+
+    out_ << "build ";
+    path_output_.WriteFile(out_, partial_info_plist);
+    out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+         << GeneralTool::kGeneralToolStamp;
+    if (!order_only_deps.empty()) {
+      out_ << " ||";
+      path_output_.WriteFiles(out_, order_only_deps);
+    }
+    out_ << std::endl;
+    return;
+  }
+
+  OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp(
+      target_->bundle_data().assets_catalog_deps());
+  DCHECK(!input_dep.value().empty());
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, compiled_catalog);
+  if (partial_info_plist != OutputFile()) {
+    // If "partial_info_plist" is non-empty, then add it to list of implicit
+    // outputs of the asset catalog compilation, so that target can use it
+    // without getting the ninja error "'foo', needed by 'bar', missing and
+    // no known rule to make it".
+    out_ << " | ";
+    path_output_.WriteFile(out_, partial_info_plist);
+  }
+
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolCompileXCAssets;
+
+  SourceFileSet asset_catalog_bundles;
+  for (const auto& source : target_->bundle_data().assets_catalog_sources()) {
+    out_ << " ";
+    path_output_.WriteFile(out_, source);
+    asset_catalog_bundles.insert(source);
+  }
+
+  out_ << " | ";
+  path_output_.WriteFile(out_, input_dep);
+
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
+  }
+
+  out_ << std::endl;
+
+  out_ << "  product_type = " << target_->bundle_data().product_type()
+       << std::endl;
+
+  if (partial_info_plist != OutputFile()) {
+    out_ << "  partial_info_plist = ";
+    path_output_.WriteFile(out_, partial_info_plist);
+    out_ << std::endl;
+  }
+
+  const std::vector<SubstitutionPattern>& flags =
+      target_->bundle_data().xcasset_compiler_flags().list();
+  if (!flags.empty()) {
+    out_ << "  " << SubstitutionXcassetsCompilerFlags.ninja_name << " =";
+    EscapeOptions args_escape_options;
+    args_escape_options.mode = ESCAPE_NINJA_COMMAND;
+    for (const auto& flag : flags) {
+      out_ << " ";
+      SubstitutionWriter::WriteWithNinjaVariables(
+          flag, args_escape_options, out_);
+    }
+    out_ << std::endl;
+  }
+}
+
+OutputFile
+NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp(
+    const std::vector<const Target*>& dependencies) {
+  DCHECK(!dependencies.empty());
+  if (dependencies.size() == 1)
+    return dependencies[0]->dependency_output_file();
+
+  OutputFile xcassets_input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  xcassets_input_stamp_file.value().append(target_->label().name());
+  xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, xcassets_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+
+  for (const Target* target : dependencies) {
+    out_ << " ";
+    path_output_.WriteFile(out_, target->dependency_output_file());
+  }
+  out_ << std::endl;
+  return xcassets_input_stamp_file;
+}
+
+void NinjaCreateBundleTargetWriter::WriteCodeSigningStep(
+    const std::string& code_signing_rule_name,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* output_files) {
+  if (code_signing_rule_name.empty())
+    return;
+
+  OutputFile code_signing_input_stamp_file =
+      WriteCodeSigningInputDepsStamp(order_only_deps, output_files);
+  DCHECK(!code_signing_input_stamp_file.value().empty());
+
+  out_ << "build";
+  std::vector<OutputFile> code_signing_output_files;
+  SubstitutionWriter::GetListAsOutputFiles(
+      settings_, target_->bundle_data().code_signing_outputs(),
+      &code_signing_output_files);
+  path_output_.WriteFiles(out_, code_signing_output_files);
+
+  // Since the code signature step depends on all the files from the bundle,
+  // the create_bundle stamp can just depends on the output of the signature
+  // script (dependencies are transitive).
+  *output_files = std::move(code_signing_output_files);
+
+  out_ << ": " << code_signing_rule_name;
+  out_ << " | ";
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
+  out_ << std::endl;
+}
+
+OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsStamp(
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* output_files) {
+  std::vector<SourceFile> code_signing_input_files;
+  code_signing_input_files.push_back(
+      target_->bundle_data().code_signing_script());
+  code_signing_input_files.insert(
+      code_signing_input_files.end(),
+      target_->bundle_data().code_signing_sources().begin(),
+      target_->bundle_data().code_signing_sources().end());
+  for (const OutputFile& output_file : *output_files) {
+    code_signing_input_files.push_back(
+        output_file.AsSourceFile(settings_->build_settings()));
+  }
+
+  DCHECK(!code_signing_input_files.empty());
+  if (code_signing_input_files.size() == 1 && order_only_deps.empty())
+    return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
+
+  OutputFile code_signing_input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  code_signing_input_stamp_file.value().append(target_->label().name());
+  code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+
+  for (const SourceFile& source : code_signing_input_files) {
+    out_ << " ";
+    path_output_.WriteFile(out_, source);
+  }
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
+  }
+  out_ << std::endl;
+  return code_signing_input_stamp_file;
+}
diff --git a/src/gn/ninja_create_bundle_target_writer.h b/src/gn/ninja_create_bundle_target_writer.h
new file mode 100644 (file)
index 0000000..6d4e224
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_CREATE_BUNDLE_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_CREATE_BUNDLE_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+class BundleFileRule;
+
+// Writes a .ninja file for a bundle_data target type.
+class NinjaCreateBundleTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaCreateBundleTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaCreateBundleTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  // Writes the Ninja rule for invoking the code signing script.
+  //
+  // Returns the name of the custom rule generated for the code signing step if
+  // defined, otherwise returns an empty string.
+  std::string WriteCodeSigningRuleDefinition();
+
+  // Writes the steps to copy files into the bundle.
+  //
+  // The list of newly created files will be added to |output_files|.
+  void WriteCopyBundleDataSteps(const std::vector<OutputFile>& order_only_deps,
+                                std::vector<OutputFile>* output_files);
+
+  // Writes the step to copy files BundleFileRule into the bundle.
+  //
+  // The list of newly created files will be added to |output_files|.
+  void WriteCopyBundleFileRuleSteps(
+      const BundleFileRule& file_rule,
+      const std::vector<OutputFile>& order_only_deps,
+      std::vector<OutputFile>* output_files);
+
+  // Writes the step to compile assets catalogs.
+  //
+  // The list of newly created files will be added to |output_files|.
+  void WriteCompileAssetsCatalogStep(
+      const std::vector<OutputFile>& order_only_deps,
+      std::vector<OutputFile>* output_files);
+
+  // Writes the stamp file for the assets catalog compilation input
+  // dependencies.
+  OutputFile WriteCompileAssetsCatalogInputDepsStamp(
+      const std::vector<const Target*>& dependencies);
+
+  // Writes the code signing step (if a script is defined).
+  //
+  // The list of newly created files will be added to |output_files|. As the
+  // code signing may depends on the full bundle structure, this step will
+  // depends on all files generated via other rules.
+  void WriteCodeSigningStep(const std::string& code_signing_rule_name,
+                            const std::vector<OutputFile>& order_only_deps,
+                            std::vector<OutputFile>* output_files);
+
+  // Writes the stamp file for the code signing input dependencies.
+  OutputFile WriteCodeSigningInputDepsStamp(
+      const std::vector<OutputFile>& order_only_deps,
+      std::vector<OutputFile>* output_files);
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaCreateBundleTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_CREATE_BUNDLE_TARGET_WRITER_H_
diff --git a/src/gn/ninja_create_bundle_target_writer_unittest.cc b/src/gn/ninja_create_bundle_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..d2c5c24
--- /dev/null
@@ -0,0 +1,487 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_create_bundle_target_writer.h"
+
+#include <algorithm>
+#include <memory>
+#include <sstream>
+
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+void SetupBundleDataDir(BundleData* bundle_data, const std::string& root_dir) {
+  std::string bundle_root_dir = root_dir + "/bar.bundle";
+  bundle_data->root_dir() = SourceDir(bundle_root_dir);
+  bundle_data->contents_dir() = SourceDir(bundle_root_dir + "/Contents");
+  bundle_data->resources_dir() =
+      SourceDir(bundle_data->contents_dir().value() + "/Resources");
+  bundle_data->executable_dir() =
+      SourceDir(bundle_data->contents_dir().value() + "/MacOS");
+}
+
+std::unique_ptr<Target> NewAction(const TestWithScope& setup) {
+  Err err;
+  auto action = std::make_unique<Target>(setup.settings(),
+                                         Label(SourceDir("//foo/"), "bar"));
+  action->set_output_type(Target::ACTION);
+  action->visibility().SetPublic();
+  action->action_values().set_script(SourceFile("//foo/script.py"));
+
+  action->action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+
+  action->SetToolchain(setup.toolchain());
+  return action;
+}
+
+}  // namespace
+
+// Tests multiple files with an output pattern.
+TEST(NinjaCreateBundleTargetWriter, Run) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
+      "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+      "bar.bundle/Contents/Resources/input1.txt "
+      "bar.bundle/Contents/Resources/input2.txt"
+      " || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests creating a bundle in a sub-directory of $root_out_dir.
+TEST(NinjaCreateBundleTargetWriter, InSubDirectory) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug/gen");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
+      "build gen/bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build gen/bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+      "gen/bar.bundle/Contents/Resources/input1.txt "
+      "gen/bar.bundle/Contents/Resources/input2.txt || "
+      "obj/baz/bar.inputdeps.stamp\n"
+      "build gen/bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests empty asset catalog with partial_info_plist property defined.
+TEST(NinjaCreateBundleTargetWriter, JustPartialInfoPlist) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.bundle_data().product_type().assign("com.apple.product-type");
+  create_bundle.bundle_data().set_partial_info_plist(
+      SourceFile("//out/Debug/baz/bar/bar_partial_info.plist"));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build baz/bar/bar_partial_info.plist: stamp || obj/foo/bar.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+      "baz/bar/bar_partial_info.plist || obj/foo/bar.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests multiple files from asset catalog.
+TEST(NinjaCreateBundleTargetWriter, AssetCatalog) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.colorset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.dataset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.dataset/FooScript.js"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.bundle_data().product_type().assign("com.apple.product-type");
+  create_bundle.bundle_data().xcasset_compiler_flags() =
+      SubstitutionList::MakeForTest("--app-icon", "foo");
+
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
+      "build bar.bundle/Contents/Resources/Assets.car: compile_xcassets "
+      "../../foo/Foo.xcassets | obj/foo/data.stamp || "
+      "obj/baz/bar.inputdeps.stamp\n"
+      "  product_type = com.apple.product-type\n"
+      "  xcasset_compiler_flags = --app-icon foo\n"
+      "build obj/baz/bar.stamp: stamp "
+      "bar.bundle/Contents/Resources/Assets.car || "
+      "obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests that the phony target for the top-level bundle directory is generated
+// correctly.
+TEST(NinjaCreateBundleTargetWriter, PhonyTarget) {
+  Err err;
+  TestWithScope setup;
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.stamp: stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests complex target with multiple bundle_data sources, including
+// some asset catalog.
+TEST(NinjaCreateBundleTargetWriter, Complex) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target bundle_data0(setup.settings(),
+                      Label(SourceDir("//qux/"), "info_plist"));
+  bundle_data0.set_output_type(Target::BUNDLE_DATA);
+  bundle_data0.sources().push_back(SourceFile("//qux/qux-Info.plist"));
+  bundle_data0.action_values().outputs() =
+      SubstitutionList::MakeForTest("{{bundle_contents_dir}}/Info.plist");
+  bundle_data0.SetToolchain(setup.toolchain());
+  bundle_data0.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data0.OnResolved(&err));
+
+  Target bundle_data1(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data1.set_output_type(Target::BUNDLE_DATA);
+  bundle_data1.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data1.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data1.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data1.SetToolchain(setup.toolchain());
+  bundle_data1.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data1.OnResolved(&err));
+
+  Target bundle_data2(setup.settings(), Label(SourceDir("//foo/"), "assets"));
+  bundle_data2.set_output_type(Target::BUNDLE_DATA);
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.colorset/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.dataset/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.dataset/FooScript.js"));
+  bundle_data2.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data2.SetToolchain(setup.toolchain());
+  bundle_data2.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data2.OnResolved(&err));
+
+  Target bundle_data3(setup.settings(), Label(SourceDir("//quz/"), "assets"));
+  bundle_data3.set_output_type(Target::BUNDLE_DATA);
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/Contents.json"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/Contents.json"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29.png"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29@2x.png"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29@3x.png"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.dataset/Contents.json"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.dataset/QuzScript.js"));
+  bundle_data3.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data3.SetToolchain(setup.toolchain());
+  bundle_data3.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data3.OnResolved(&err));
+
+  Target bundle_data4(setup.settings(), Label(SourceDir("//biz/"), "assets"));
+  bundle_data4.set_output_type(Target::BUNDLE_DATA);
+  bundle_data4.sources().push_back(
+      SourceFile("//biz/Biz.xcassets/Contents.json"));
+  bundle_data4.sources().push_back(
+      SourceFile("//biz/Biz.xcassets/biz.colorset/Contents.json"));
+  bundle_data4.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data4.SetToolchain(setup.toolchain());
+  bundle_data4.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data4.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data0));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data1));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data2));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data3));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data4));
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.bundle_data().product_type().assign("com.apple.product-type");
+  create_bundle.bundle_data().set_partial_info_plist(
+      SourceFile("//out/Debug/baz/bar/bar_partial_info.plist"));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/biz/assets.stamp "
+      "obj/foo/assets.stamp obj/foo/bar.stamp obj/foo/data.stamp "
+      "obj/qux/info_plist.stamp obj/quz/assets.stamp\n"
+      "build bar.bundle/Contents/Info.plist: copy_bundle_data "
+      "../../qux/qux-Info.plist || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.xcassets.inputdeps.stamp: stamp "
+      "obj/foo/assets.stamp "
+      "obj/quz/assets.stamp obj/biz/assets.stamp\n"
+      "build bar.bundle/Contents/Resources/Assets.car | "
+      "baz/bar/bar_partial_info.plist: compile_xcassets "
+      "../../foo/Foo.xcassets ../../quz/Quz.xcassets "
+      "../../biz/Biz.xcassets | obj/baz/bar.xcassets.inputdeps.stamp || "
+      "obj/baz/bar.inputdeps.stamp\n"
+      "  product_type = com.apple.product-type\n"
+      "  partial_info_plist = baz/bar/bar_partial_info.plist\n"
+      "build obj/baz/bar.stamp: stamp "
+      "bar.bundle/Contents/Info.plist "
+      "bar.bundle/Contents/Resources/input1.txt "
+      "bar.bundle/Contents/Resources/input2.txt "
+      "bar.bundle/Contents/Resources/Assets.car "
+      "baz/bar/bar_partial_info.plist || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests code signing steps.
+TEST(NinjaCreateBundleTargetWriter, CodeSigning) {
+  Err err;
+  TestWithScope setup;
+
+  std::unique_ptr<Target> action = NewAction(setup);
+  ASSERT_TRUE(action->OnResolved(&err)) << err.message();
+
+  Target executable(setup.settings(), Label(SourceDir("//baz/"), "quz"));
+  executable.set_output_type(Target::EXECUTABLE);
+  executable.sources().push_back(SourceFile("//baz/quz.c"));
+  executable.SetToolchain(setup.toolchain());
+  executable.visibility().SetPublic();
+  ASSERT_TRUE(executable.OnResolved(&err));
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.bundle_data().set_code_signing_script(
+      SourceFile("//build/codesign.py"));
+  create_bundle.bundle_data().code_signing_sources().push_back(
+      SourceFile("//out/Debug/quz"));
+  create_bundle.bundle_data().code_signing_outputs() =
+      SubstitutionList::MakeForTest(
+          "//out/Debug/bar.bundle/Contents/quz",
+          "//out/Debug/bar.bundle/_CodeSignature/CodeResources");
+  create_bundle.bundle_data().code_signing_args() =
+      SubstitutionList::MakeForTest("-b=quz", "bar.bundle");
+  create_bundle.public_deps().push_back(LabelTargetPair(&executable));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.private_deps().push_back(LabelTargetPair(action.get()));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.inputdeps.stamp: stamp ./quz obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
+      "rule __baz_bar___toolchain_default__code_signing_rule\n"
+      "  command =  ../../build/codesign.py -b=quz bar.bundle\n"
+      "  description = CODE SIGNING //baz:bar(//toolchain:default)\n"
+      "  restat = 1\n"
+      "\n"
+      "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.codesigning.inputdeps.stamp: stamp "
+      "../../build/codesign.py "
+      "quz "
+      "bar.bundle/Contents/Resources/input1.txt "
+      "bar.bundle/Contents/Resources/input2.txt || "
+      "obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle/Contents/quz bar.bundle/_CodeSignature/CodeResources: "
+      "__baz_bar___toolchain_default__code_signing_rule "
+      "| obj/baz/bar.codesigning.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+      "bar.bundle/Contents/quz "
+      "bar.bundle/_CodeSignature/CodeResources || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
diff --git a/src/gn/ninja_generated_file_target_writer.cc b/src/gn/ninja_generated_file_target_writer.cc
new file mode 100644 (file)
index 0000000..afe1e08
--- /dev/null
@@ -0,0 +1,97 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_generated_file_target_writer.h"
+
+#include "base/strings/string_util.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/output_conversion.h"
+#include "gn/output_file.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/string_utils.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+
+NinjaGeneratedFileTargetWriter::NinjaGeneratedFileTargetWriter(
+    const Target* target,
+    std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaGeneratedFileTargetWriter::~NinjaGeneratedFileTargetWriter() = default;
+
+void NinjaGeneratedFileTargetWriter::Run() {
+  // Write the file.
+  GenerateFile();
+
+  // A generated_file target should generate a stamp file with dependencies
+  // on each of the deps and data_deps in the target. The actual collection is
+  // done at gen time, and so ninja doesn't need to know about it.
+  std::vector<OutputFile> output_files;
+  std::vector<OutputFile> data_output_files;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_output_files.push_back(pair.ptr->dependency_output_file());
+    } else {
+      output_files.push_back(pair.ptr->dependency_output_file());
+    }
+  }
+
+  const LabelTargetVector& data_deps = target_->data_deps();
+  for (const auto& pair : data_deps)
+    data_output_files.push_back(pair.ptr->dependency_output_file());
+
+  WriteStampForTarget(output_files, data_output_files);
+}
+
+void NinjaGeneratedFileTargetWriter::GenerateFile() {
+  Err err;
+
+  // If this is a metadata target, populate the write value with the appropriate
+  // data.
+  Value contents;
+  if (target_->contents().type() == Value::NONE) {
+    // Origin is set to the outputs location, so that errors with this value
+    // get flagged on the right target.
+    CHECK(target_->action_values().outputs().list().size() == 1U);
+    contents = Value(target_->action_values().outputs().list()[0].origin(),
+                     Value::LIST);
+    std::set<const Target*> targets_walked;
+    if (!target_->GetMetadata(target_->data_keys(), target_->walk_keys(),
+                              target_->rebase(), /*deps_only = */ true,
+                              &contents.list_value(), &targets_walked, &err)) {
+      g_scheduler->FailWithError(err);
+      return;
+    }
+  } else {
+    contents = target_->contents();
+  }
+
+  std::vector<SourceFile> outputs_as_sources;
+  target_->action_values().GetOutputsAsSourceFiles(target_,
+                                                   &outputs_as_sources);
+  CHECK(outputs_as_sources.size() == 1);
+
+  base::FilePath output =
+      settings_->build_settings()->GetFullPath(outputs_as_sources[0]);
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, outputs_as_sources[0].value());
+
+  // Compute output.
+  std::ostringstream out;
+  ConvertValueToOutput(settings_, contents, target_->output_conversion(), out,
+                       &err);
+
+  if (err.has_error()) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+
+  WriteFileIfChanged(output, out.str(), &err);
+
+  if (err.has_error()) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+}
diff --git a/src/gn/ninja_generated_file_target_writer.h b/src/gn/ninja_generated_file_target_writer.h
new file mode 100644 (file)
index 0000000..43231a6
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_GENERATED_FILE_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_GENERATED_FILE_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a group target type.
+class NinjaGeneratedFileTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaGeneratedFileTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaGeneratedFileTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  void GenerateFile();
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaGeneratedFileTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_GENERATED_FILE_TARGET_WRITER_H_
diff --git a/src/gn/ninja_generated_file_target_writer_unittest.cc b/src/gn/ninja_generated_file_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..676ba01
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_generated_file_target_writer.h"
+
+#include "gn/source_file.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using NinjaGeneratedFileTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaGeneratedFileTargetWriterTest, Run) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::GENERATED_FILE);
+  target.visibility().SetPublic();
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.json");
+  target.set_contents(Value(nullptr, true));
+  target.set_output_conversion(Value(nullptr, "json"));
+
+  Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
+  dep.set_output_type(Target::ACTION);
+  dep.visibility().SetPublic();
+  dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target dep2(setup.settings(), Label(SourceDir("//foo/"), "dep2"));
+  dep2.set_output_type(Target::ACTION);
+  dep2.visibility().SetPublic();
+  dep2.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep2.OnResolved(&err));
+
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
+  datadep.set_output_type(Target::ACTION);
+  datadep.visibility().SetPublic();
+  datadep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.public_deps().push_back(LabelTargetPair(&bundle_data_dep));
+  target.data_deps().push_back(LabelTargetPair(&datadep));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err)) << err.message();
+
+  std::ostringstream out;
+  NinjaGeneratedFileTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
+      "obj/foo/bundle_data_dep.stamp obj/foo/datadep.stamp\n";
+  EXPECT_EQ(expected, out.str());
+}
diff --git a/src/gn/ninja_group_target_writer.cc b/src/gn/ninja_group_target_writer.cc
new file mode 100644 (file)
index 0000000..beeb54a
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_group_target_writer.h"
+
+#include "base/strings/string_util.h"
+#include "gn/deps_iterator.h"
+#include "gn/output_file.h"
+#include "gn/string_utils.h"
+#include "gn/target.h"
+
+NinjaGroupTargetWriter::NinjaGroupTargetWriter(const Target* target,
+                                               std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaGroupTargetWriter::~NinjaGroupTargetWriter() = default;
+
+void NinjaGroupTargetWriter::Run() {
+  // A group rule just generates a stamp file with dependencies on each of
+  // the deps and data_deps in the group.
+  std::vector<OutputFile> output_files;
+  std::vector<OutputFile> data_output_files;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_output_files.push_back(pair.ptr->dependency_output_file());
+    } else {
+      output_files.push_back(pair.ptr->dependency_output_file());
+    }
+  }
+
+  const LabelTargetVector& data_deps = target_->data_deps();
+  for (const auto& pair : data_deps)
+    data_output_files.push_back(pair.ptr->dependency_output_file());
+
+  WriteStampForTarget(output_files, data_output_files);
+}
diff --git a/src/gn/ninja_group_target_writer.h b/src/gn/ninja_group_target_writer.h
new file mode 100644 (file)
index 0000000..8abb20a
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_GROUP_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_GROUP_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a group target type.
+class NinjaGroupTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaGroupTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaGroupTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NinjaGroupTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_GROUP_TARGET_WRITER_H_
diff --git a/src/gn/ninja_group_target_writer_unittest.cc b/src/gn/ninja_group_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..1fe51ca
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_group_target_writer.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(NinjaGroupTargetWriter, Run) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::GROUP);
+  target.visibility().SetPublic();
+
+  Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
+  dep.set_output_type(Target::ACTION);
+  dep.visibility().SetPublic();
+  dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target dep2(setup.settings(), Label(SourceDir("//foo/"), "dep2"));
+  dep2.set_output_type(Target::ACTION);
+  dep2.visibility().SetPublic();
+  dep2.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep2.OnResolved(&err));
+
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
+  datadep.set_output_type(Target::ACTION);
+  datadep.visibility().SetPublic();
+  datadep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.public_deps().push_back(LabelTargetPair(&bundle_data_dep));
+  target.data_deps().push_back(LabelTargetPair(&datadep));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaGroupTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
+      "obj/foo/bundle_data_dep.stamp obj/foo/datadep.stamp\n";
+  EXPECT_EQ(expected, out.str());
+}
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
new file mode 100644 (file)
index 0000000..38f4686
--- /dev/null
@@ -0,0 +1,323 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_rust_binary_target_writer.h"
+
+#include <sstream>
+
+#include "base/strings/string_util.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/ninja_utils.h"
+#include "gn/rust_substitution_type.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+namespace {
+
+// Returns the proper escape options for writing compiler and linker flags.
+EscapeOptions GetFlagOptions() {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  return opts;
+}
+
+void WriteVar(const char* name,
+              const std::string& value,
+              EscapeOptions opts,
+              std::ostream& out) {
+  out << name << " = ";
+  EscapeStringToStream(out, value, opts);
+  out << std::endl;
+}
+
+void WriteCrateVars(const Target* target,
+                    const Tool* tool,
+                    EscapeOptions opts,
+                    std::ostream& out) {
+  WriteVar(kRustSubstitutionCrateName.ninja_name,
+           target->rust_values().crate_name(), opts, out);
+
+  std::string crate_type;
+  switch (target->rust_values().crate_type()) {
+    // Auto-select the crate type for executables, static libraries, and rlibs.
+    case RustValues::CRATE_AUTO: {
+      switch (target->output_type()) {
+        case Target::EXECUTABLE:
+          crate_type = "bin";
+          break;
+        case Target::STATIC_LIBRARY:
+          crate_type = "staticlib";
+          break;
+        case Target::RUST_LIBRARY:
+          crate_type = "rlib";
+          break;
+        case Target::RUST_PROC_MACRO:
+          crate_type = "proc-macro";
+          break;
+        default:
+          NOTREACHED();
+      }
+      break;
+    }
+    case RustValues::CRATE_BIN:
+      crate_type = "bin";
+      break;
+    case RustValues::CRATE_CDYLIB:
+      crate_type = "cdylib";
+      break;
+    case RustValues::CRATE_DYLIB:
+      crate_type = "dylib";
+      break;
+    case RustValues::CRATE_PROC_MACRO:
+      crate_type = "proc-macro";
+      break;
+    case RustValues::CRATE_RLIB:
+      crate_type = "rlib";
+      break;
+    case RustValues::CRATE_STATICLIB:
+      crate_type = "staticlib";
+      break;
+    default:
+      NOTREACHED();
+  }
+  WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, out);
+
+  WriteVar(SubstitutionOutputExtension.ninja_name,
+           SubstitutionWriter::GetLinkerSubstitution(
+               target, tool, &SubstitutionOutputExtension),
+           opts, out);
+  WriteVar(SubstitutionOutputDir.ninja_name,
+           SubstitutionWriter::GetLinkerSubstitution(target, tool,
+                                                     &SubstitutionOutputDir),
+           opts, out);
+}
+
+}  // namespace
+
+NinjaRustBinaryTargetWriter::NinjaRustBinaryTargetWriter(const Target* target,
+                                                         std::ostream& out)
+    : NinjaBinaryTargetWriter(target, out),
+      tool_(target->toolchain()->GetToolForTargetFinalOutputAsRust(target)) {}
+
+NinjaRustBinaryTargetWriter::~NinjaRustBinaryTargetWriter() = default;
+
+// TODO(juliehockett): add inherited library support? and IsLinkable support?
+// for c-cross-compat
+void NinjaRustBinaryTargetWriter::Run() {
+  DCHECK(target_->output_type() != Target::SOURCE_SET);
+
+  size_t num_stamp_uses = target_->sources().size();
+
+  std::vector<OutputFile> input_deps = WriteInputsStampAndGetDep(
+      num_stamp_uses);
+
+  WriteCompilerVars();
+
+  // Classify our dependencies.
+  ClassifiedDeps classified_deps = GetClassifiedDeps();
+
+  // The input dependencies will be an order-only dependency. This will cause
+  // Ninja to make sure the inputs are up to date before compiling this source,
+  // but changes in the inputs deps won't cause the file to be recompiled. See
+  // the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation.
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+  std::copy(input_deps.begin(), input_deps.end(),
+            std::back_inserter(order_only_deps));
+
+  // Build lists which will go into different bits of the rustc command line.
+  // Public rust_library deps go in a --extern rlibs, public non-rust deps go in
+  // -Ldependency. Also assemble a list of extra (i.e. implicit) deps
+  // for ninja dependency tracking.
+  UniqueVector<OutputFile> implicit_deps;
+  AppendSourcesAndInputsToImplicitDeps(&implicit_deps);
+  implicit_deps.Append(classified_deps.extra_object_files.begin(),
+                       classified_deps.extra_object_files.end());
+
+  std::vector<OutputFile> rustdeps;
+  std::vector<OutputFile> nonrustdeps;
+  nonrustdeps.insert(nonrustdeps.end(),
+                     classified_deps.extra_object_files.begin(),
+                     classified_deps.extra_object_files.end());
+  for (const auto* framework_dep : classified_deps.framework_deps) {
+    order_only_deps.push_back(framework_dep->dependency_output_file());
+  }
+  for (const auto* non_linkable_dep : classified_deps.non_linkable_deps) {
+    if (non_linkable_dep->source_types_used().RustSourceUsed() &&
+        non_linkable_dep->output_type() != Target::SOURCE_SET) {
+      rustdeps.push_back(non_linkable_dep->dependency_output_file());
+    }
+    order_only_deps.push_back(non_linkable_dep->dependency_output_file());
+  }
+  for (const auto* linkable_dep : classified_deps.linkable_deps) {
+    if (linkable_dep->source_types_used().RustSourceUsed()) {
+      rustdeps.push_back(linkable_dep->link_output_file());
+    } else {
+      nonrustdeps.push_back(linkable_dep->link_output_file());
+    }
+    implicit_deps.push_back(linkable_dep->dependency_output_file());
+  }
+
+  // Rust libraries specified by paths.
+  for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+    const ConfigValues& cur = iter.cur();
+    for (const auto& e : cur.externs()) {
+      if (e.second.is_source_file()) {
+        implicit_deps.push_back(
+            OutputFile(settings_->build_settings(), e.second.source_file()));
+      }
+    }
+  }
+
+  // Bubble up the full list of transitive rlib dependencies.
+  std::vector<OutputFile> transitive_rustlibs;
+  for (const auto* dep :
+       target_->rust_values().transitive_libs().GetOrdered()) {
+    if (dep->source_types_used().RustSourceUsed()) {
+      transitive_rustlibs.push_back(dep->dependency_output_file());
+    }
+  }
+
+  std::vector<OutputFile> tool_outputs;
+  SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+      target_, tool_, tool_->outputs(), &tool_outputs);
+  WriteCompilerBuildLine({target_->rust_values().crate_root()},
+                         implicit_deps.vector(), order_only_deps, tool_->name(),
+                         tool_outputs);
+
+  std::vector<const Target*> extern_deps(
+      classified_deps.linkable_deps.vector());
+  std::copy(classified_deps.non_linkable_deps.begin(),
+            classified_deps.non_linkable_deps.end(),
+            std::back_inserter(extern_deps));
+  WriteExterns(extern_deps);
+  WriteRustdeps(transitive_rustlibs, rustdeps, nonrustdeps);
+  WriteSourcesAndInputs();
+}
+
+void NinjaRustBinaryTargetWriter::WriteCompilerVars() {
+  const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
+  EscapeOptions opts = GetFlagOptions();
+  WriteCrateVars(target_, tool_, opts, out_);
+
+  WriteOneFlag(target_, &kRustSubstitutionRustFlags, false, Tool::kToolNone,
+               &ConfigValues::rustflags, opts, path_output_, out_);
+
+  WriteOneFlag(target_, &kRustSubstitutionRustEnv, false, Tool::kToolNone,
+               &ConfigValues::rustenv, opts, path_output_, out_);
+
+  WriteSharedVars(subst);
+}
+
+void NinjaRustBinaryTargetWriter::AppendSourcesAndInputsToImplicitDeps(
+    UniqueVector<OutputFile>* deps) const {
+  // Only the crate_root file needs to be given to rustc as input.
+  // Any other 'sources' are just implicit deps.
+  // Most Rust targets won't bother specifying the "sources =" line
+  // because it is handled sufficiently by crate_root and the generation
+  // of depfiles by rustc. But for those which do...
+  for (const auto& source : target_->sources()) {
+    deps->push_back(OutputFile(settings_->build_settings(), source));
+  }
+  for (const auto& data : target_->config_values().inputs()) {
+    deps->push_back(OutputFile(settings_->build_settings(), data));
+  }
+}
+
+void NinjaRustBinaryTargetWriter::WriteSourcesAndInputs() {
+  out_ << "  sources =";
+  for (const auto& source : target_->sources()) {
+    out_ << " ";
+    path_output_.WriteFile(out_, OutputFile(settings_->build_settings(), source));
+  }
+  for (const auto& data : target_->config_values().inputs()) {
+    out_ << " ";
+    path_output_.WriteFile(out_, OutputFile(settings_->build_settings(), data));
+  }
+  out_ << std::endl;
+}
+
+void NinjaRustBinaryTargetWriter::WriteExterns(
+    const std::vector<const Target*>& deps) {
+  out_ << "  externs =";
+
+  for (const Target* target : deps) {
+    if (target->output_type() == Target::RUST_LIBRARY ||
+        target->output_type() == Target::RUST_PROC_MACRO) {
+      out_ << " --extern ";
+      const auto& renamed_dep =
+          target_->rust_values().aliased_deps().find(target->label());
+      if (renamed_dep != target_->rust_values().aliased_deps().end()) {
+        out_ << renamed_dep->second << "=";
+      } else {
+        out_ << std::string(target->rust_values().crate_name()) << "=";
+      }
+      path_output_.WriteFile(out_, target->dependency_output_file());
+    }
+  }
+
+  EscapeOptions extern_escape_opts;
+  extern_escape_opts.mode = ESCAPE_NINJA_COMMAND;
+
+  for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+    const ConfigValues& cur = iter.cur();
+    for (const auto& e : cur.externs()) {
+      out_ << " --extern " << std::string(e.first) << "=";
+      if (e.second.is_source_file()) {
+        path_output_.WriteFile(out_, e.second.source_file());
+      } else {
+        EscapeStringToStream(out_, e.second.value(), extern_escape_opts);
+      }
+    }
+  }
+
+  out_ << std::endl;
+}
+
+void NinjaRustBinaryTargetWriter::WriteRustdeps(
+    const std::vector<OutputFile>& transitive_rustdeps,
+    const std::vector<OutputFile>& rustdeps,
+    const std::vector<OutputFile>& nonrustdeps) {
+  out_ << "  rustdeps =";
+
+  // Rust dependencies.
+  UniqueVector<SourceDir> transitive_rustdep_dirs;
+  for (const auto& rustdep : transitive_rustdeps) {
+    // TODO switch to using --extern priv: after stabilization
+    transitive_rustdep_dirs.push_back(
+        rustdep.AsSourceFile(settings_->build_settings()).GetDir());
+  }
+  for (const auto& rustdepdir : transitive_rustdep_dirs) {
+    out_ << " -Ldependency=";
+    path_output_.WriteDir(out_, rustdepdir, PathOutput::DIR_NO_LAST_SLASH);
+  }
+
+  EscapeOptions lib_escape_opts;
+  lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
+
+  // Non-Rust native dependencies.
+  UniqueVector<SourceDir> nonrustdep_dirs;
+  for (const auto& nonrustdep : nonrustdeps) {
+    nonrustdep_dirs.push_back(
+        nonrustdep.AsSourceFile(settings_->build_settings()).GetDir());
+  }
+  // First -Lnative to specify the search directories.
+  // This is necessary for #[link(...)] directives to work properly.
+  for (const auto& nonrustdep_dir : nonrustdep_dirs) {
+    out_ << " -Lnative=";
+    path_output_.WriteDir(out_, nonrustdep_dir, PathOutput::DIR_NO_LAST_SLASH);
+  }
+  for (const auto& nonrustdep : nonrustdeps) {
+    out_ << " -Clink-arg=";
+    path_output_.WriteFile(out_, nonrustdep);
+  }
+
+  WriteLinkerFlags(out_, tool_, nullptr);
+  WriteLibs(out_, tool_);
+  out_ << std::endl;
+}
diff --git a/src/gn/ninja_rust_binary_target_writer.h b/src/gn/ninja_rust_binary_target_writer.h
new file mode 100644 (file)
index 0000000..bd060ab
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_RUST_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_RUST_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "gn/ninja_binary_target_writer.h"
+#include "gn/rust_tool.h"
+
+struct EscapeOptions;
+
+// Writes a .ninja file for a binary target type (an executable, a shared
+// library, or a static library).
+class NinjaRustBinaryTargetWriter : public NinjaBinaryTargetWriter {
+ public:
+  NinjaRustBinaryTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaRustBinaryTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  void WriteCompilerVars();
+  void WriteSources(const OutputFile& input_dep,
+                    const std::vector<OutputFile>& order_only_deps);
+  void WriteExterns(const std::vector<const Target*>& deps);
+  void WriteRustdeps(const std::vector<OutputFile>& transitive_rustdeps,
+                     const std::vector<OutputFile>& rustdeps,
+                     const std::vector<OutputFile>& nonrustdeps);
+  // Unlike C/C++, Rust compiles all sources of a crate in one command.
+  // Write a ninja variable `sources` that contains all sources and input files.
+  void WriteSourcesAndInputs();
+  void WriteEdition();
+  void AppendSourcesAndInputsToImplicitDeps(UniqueVector<OutputFile>* deps) const;
+
+  const RustTool* tool_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaRustBinaryTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_RUST_BINARY_TARGET_WRITER_H_
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..6840b72
--- /dev/null
@@ -0,0 +1,892 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_rust_binary_target_writer.h"
+
+#include "gn/config.h"
+#include "gn/rust_values.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using NinjaRustBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustSourceSet) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//foo/input1.rs"));
+  target.sources().push_back(SourceFile("//foo/main.rs"));
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_FALSE(target.OnResolved(&err));
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustExecutable) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/input3.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input3.rs "
+        "../../foo/main.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../foo/input3.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RlibDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mylib\n"
+        "crate_type = rlib\n"
+        "output_extension = .rlib\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmylib\n"
+        "\n"
+        "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target another_rlib(setup.settings(), Label(SourceDir("//foo/"), "direct"));
+  another_rlib.set_output_type(Target::RUST_LIBRARY);
+  another_rlib.visibility().SetPublic();
+  SourceFile lib("//foo/main.rs");
+  another_rlib.sources().push_back(SourceFile("//foo/direct.rs"));
+  another_rlib.sources().push_back(lib);
+  another_rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  another_rlib.rust_values().set_crate_root(lib);
+  another_rlib.rust_values().crate_name() = "direct";
+  another_rlib.SetToolchain(setup.toolchain());
+  another_rlib.public_deps().push_back(LabelTargetPair(&rlib));
+  ASSERT_TRUE(another_rlib.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.private_deps().push_back(LabelTargetPair(&another_rlib));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/foo/libdirect.rlib\n"
+        "  externs = --extern direct=obj/foo/libdirect.rlib\n"
+        "  rustdeps = -Ldependency=obj/foo -Ldependency=obj/bar\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RlibDepsAcrossGroups) {
+  Err err;
+  TestWithScope setup;
+
+  Target procmacro(setup.settings(), Label(SourceDir("//bar/"), "mymacro"));
+  procmacro.set_output_type(Target::RUST_PROC_MACRO);
+  procmacro.visibility().SetPublic();
+  SourceFile barproc("//bar/lib.rs");
+  procmacro.sources().push_back(SourceFile("//bar/mylib.rs"));
+  procmacro.sources().push_back(barproc);
+  procmacro.source_types_used().Set(SourceFile::SOURCE_RS);
+  procmacro.rust_values().set_crate_root(barproc);
+  procmacro.rust_values().crate_name() = "mymacro";
+  procmacro.rust_values().set_crate_type(RustValues::CRATE_PROC_MACRO);
+  procmacro.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(procmacro.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&procmacro, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mymacro\n"
+        "crate_type = proc-macro\n"
+        "output_extension = .so\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmymacro\n"
+        "\n"
+        "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target group(setup.settings(), Label(SourceDir("//baz/"), "group"));
+  group.set_output_type(Target::GROUP);
+  group.visibility().SetPublic();
+  group.public_deps().push_back(LabelTargetPair(&procmacro));
+  group.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(group.OnResolved(&err));
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.SetToolchain(setup.toolchain());
+  rlib.public_deps().push_back(LabelTargetPair(&group));
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mylib\n"
+        "crate_type = rlib\n"
+        "output_extension = .rlib\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmylib\n"
+        "\n"
+        "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs obj/bar/libmymacro.so || "
+        "obj/baz/group.stamp\n"
+        "  externs = --extern mymacro=obj/bar/libmymacro.so\n"
+        "  rustdeps = -Ldependency=obj/bar\n"
+        "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.private_deps().push_back(LabelTargetPair(&rlib));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | "
+        "../../foo/source.rs ../../foo/main.rs obj/bar/libmylib.rlib\n"
+        "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
+        "  rustdeps = -Ldependency=obj/bar\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RenamedDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target another_rlib(setup.settings(), Label(SourceDir("//foo/"), "direct"));
+  another_rlib.set_output_type(Target::RUST_LIBRARY);
+  another_rlib.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  another_rlib.sources().push_back(SourceFile("//foo/direct.rs"));
+  another_rlib.sources().push_back(lib);
+  another_rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  another_rlib.rust_values().set_crate_root(lib);
+  another_rlib.rust_values().crate_name() = "direct";
+  another_rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(another_rlib.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.rust_values().aliased_deps()[another_rlib.label()] = "direct_renamed";
+  target.private_deps().push_back(LabelTargetPair(&another_rlib));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/foo/libdirect.rlib\n"
+        "  externs = --extern direct_renamed=obj/foo/libdirect.rlib\n"
+        "  rustdeps = -Ldependency=obj/foo\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, NonRustDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target staticlib(setup.settings(), Label(SourceDir("//foo/"), "static"));
+  staticlib.set_output_type(Target::STATIC_LIBRARY);
+  staticlib.visibility().SetPublic();
+  staticlib.sources().push_back(SourceFile("//foo/static.cpp"));
+  staticlib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  staticlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(staticlib.OnResolved(&err));
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  Target sharedlib(setup.settings(), Label(SourceDir("//foo/"), "shared"));
+  sharedlib.set_output_type(Target::SHARED_LIBRARY);
+  sharedlib.visibility().SetPublic();
+  sharedlib.sources().push_back(SourceFile("//foo/static.cpp"));
+  sharedlib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  sharedlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(sharedlib.OnResolved(&err));
+
+  Target csourceset(setup.settings(), Label(SourceDir("//baz/"), "sourceset"));
+  csourceset.set_output_type(Target::SOURCE_SET);
+  csourceset.visibility().SetPublic();
+  csourceset.sources().push_back(SourceFile("//baz/csourceset.cpp"));
+  csourceset.source_types_used().Set(SourceFile::SOURCE_CPP);
+  csourceset.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(csourceset.OnResolved(&err));
+
+  Toolchain toolchain_with_toc(
+      setup.settings(), Label(SourceDir("//toolchain_with_toc/"), "with_toc"));
+  TestWithScope::SetupToolchain(&toolchain_with_toc, true);
+  Target sharedlib_with_toc(setup.settings(),
+                            Label(SourceDir("//foo/"), "shared_with_toc"));
+  sharedlib_with_toc.set_output_type(Target::SHARED_LIBRARY);
+  sharedlib_with_toc.visibility().SetPublic();
+  sharedlib_with_toc.sources().push_back(SourceFile("//foo/static.cpp"));
+  sharedlib_with_toc.source_types_used().Set(SourceFile::SOURCE_CPP);
+  sharedlib_with_toc.SetToolchain(&toolchain_with_toc);
+  ASSERT_TRUE(sharedlib_with_toc.OnResolved(&err));
+
+  Target nonrust(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  nonrust.set_output_type(Target::EXECUTABLE);
+  nonrust.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  nonrust.sources().push_back(SourceFile("//foo/source.rs"));
+  nonrust.sources().push_back(main);
+  nonrust.source_types_used().Set(SourceFile::SOURCE_RS);
+  nonrust.rust_values().set_crate_root(main);
+  nonrust.rust_values().crate_name() = "foo_bar";
+  nonrust.private_deps().push_back(LabelTargetPair(&rlib));
+  nonrust.private_deps().push_back(LabelTargetPair(&staticlib));
+  nonrust.private_deps().push_back(LabelTargetPair(&sharedlib));
+  nonrust.private_deps().push_back(LabelTargetPair(&csourceset));
+  nonrust.private_deps().push_back(LabelTargetPair(&sharedlib_with_toc));
+  nonrust.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(nonrust.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&nonrust, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/baz/sourceset.csourceset.o "
+        "obj/bar/libmylib.rlib "
+        "obj/foo/libstatic.a ./libshared.so ./libshared_with_toc.so.TOC "
+        "|| obj/baz/sourceset.stamp\n"
+        "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
+        "  rustdeps = -Ldependency=obj/bar "
+        "-Lnative=obj/baz -Lnative=obj/foo -Lnative=. "
+        "-Clink-arg=obj/baz/sourceset.csourceset.o "
+        "-Clink-arg=obj/foo/libstatic.a -Clink-arg=./libshared.so "
+        "-Clink-arg=./libshared_with_toc.so\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target nonrust_only(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  nonrust_only.set_output_type(Target::EXECUTABLE);
+  nonrust_only.visibility().SetPublic();
+  nonrust_only.sources().push_back(SourceFile("//foo/source.rs"));
+  nonrust_only.sources().push_back(main);
+  nonrust_only.source_types_used().Set(SourceFile::SOURCE_RS);
+  nonrust_only.rust_values().set_crate_root(main);
+  nonrust_only.rust_values().crate_name() = "foo_bar";
+  nonrust_only.private_deps().push_back(LabelTargetPair(&staticlib));
+  nonrust_only.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(nonrust_only.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&nonrust_only, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/foo/libstatic.a\n"
+        "  externs =\n"
+        "  rustdeps = -Lnative=obj/foo -Clink-arg=obj/foo/libstatic.a\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target rstaticlib(setup.settings(), Label(SourceDir("//baz/"), "baz"));
+  rstaticlib.set_output_type(Target::STATIC_LIBRARY);
+  rstaticlib.visibility().SetPublic();
+  SourceFile bazlib("//baz/lib.rs");
+  rstaticlib.sources().push_back(bazlib);
+  rstaticlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rstaticlib.rust_values().set_crate_root(bazlib);
+  rstaticlib.rust_values().crate_name() = "baz";
+  rstaticlib.private_deps().push_back(LabelTargetPair(&staticlib));
+  rstaticlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rstaticlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rstaticlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = baz\n"
+        "crate_type = staticlib\n"
+        "output_extension = .a\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/baz\n"
+        "target_output_name = libbaz\n"
+        "\n"
+        "build obj/baz/libbaz.a: rust_staticlib ../../baz/lib.rs | "
+        "../../baz/lib.rs "
+        "obj/foo/libstatic.a\n"
+        "  externs =\n"
+        "  rustdeps = -Lnative=obj/foo -Clink-arg=obj/foo/libstatic.a\n"
+        "  sources = ../../baz/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustOutputExtensionAndDir) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/input3.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.set_output_extension(std::string("exe"));
+  target.set_output_dir(SourceDir("//out/Debug/foo/"));
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = .exe\n"
+        "output_dir = foo\n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar.exe: rust_bin ../../foo/main.rs | ../../foo/input3.rs "
+        "../../foo/main.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../foo/input3.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, LibsAndLibDirs) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/input.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.set_output_dir(SourceDir("//out/Debug/foo/"));
+  target.config_values().libs().push_back(LibFile("quux"));
+  target.config_values().lib_dirs().push_back(SourceDir("//baz/"));
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = foo\n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input.rs "
+        "../../foo/main.rs\n"
+        "  externs =\n"
+        "  rustdeps = -Lnative=../../baz -lquux\n"
+        "  sources = ../../foo/input.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustProcMacro) {
+  Err err;
+  TestWithScope setup;
+
+  Target procmacrodep(setup.settings(),
+                      Label(SourceDir("//baz/"), "mymacrodep"));
+  procmacrodep.set_output_type(Target::RUST_LIBRARY);
+  procmacrodep.visibility().SetPublic();
+  SourceFile bazlib("//baz/lib.rs");
+  procmacrodep.sources().push_back(SourceFile("//baz/mylib.rs"));
+  procmacrodep.sources().push_back(bazlib);
+  procmacrodep.source_types_used().Set(SourceFile::SOURCE_RS);
+  procmacrodep.rust_values().set_crate_root(bazlib);
+  procmacrodep.rust_values().crate_name() = "mymacrodep";
+  procmacrodep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(procmacrodep.OnResolved(&err));
+
+  Target procmacro(setup.settings(), Label(SourceDir("//bar/"), "mymacro"));
+  procmacro.set_output_type(Target::RUST_PROC_MACRO);
+  procmacro.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  procmacro.sources().push_back(SourceFile("//bar/mylib.rs"));
+  procmacro.sources().push_back(barlib);
+  procmacro.source_types_used().Set(SourceFile::SOURCE_RS);
+  procmacro.rust_values().set_crate_root(barlib);
+  procmacro.rust_values().crate_name() = "mymacro";
+  procmacro.rust_values().set_crate_type(RustValues::CRATE_PROC_MACRO);
+  // Add a dependency to the procmacro so we can be sure its output
+  // directory is not propagated downstream beyond the proc macro.
+  procmacro.private_deps().push_back(LabelTargetPair(&procmacrodep));
+  procmacro.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(procmacro.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&procmacro, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mymacro\n"
+        "crate_type = proc-macro\n"
+        "output_extension = .so\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmymacro\n"
+        "\n"
+        "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs obj/baz/libmymacrodep.rlib\n"
+        "  externs = --extern mymacrodep=obj/baz/libmymacrodep.rlib\n"
+        "  rustdeps = -Ldependency=obj/baz\n"
+        "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.private_deps().push_back(LabelTargetPair(&procmacro));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/bar/libmymacro.so\n"
+        "  externs = --extern mymacro=obj/bar/libmymacro.so\n"
+        "  rustdeps = -Ldependency=obj/bar\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, GroupDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mylib\n"
+        "crate_type = rlib\n"
+        "output_extension = .rlib\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmylib\n"
+        "\n"
+        "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  Target group(setup.settings(), Label(SourceDir("//baz/"), "group"));
+  group.set_output_type(Target::GROUP);
+  group.visibility().SetPublic();
+  group.public_deps().push_back(LabelTargetPair(&rlib));
+  group.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(group.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.private_deps().push_back(LabelTargetPair(&group));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/bar/libmylib.rlib || obj/baz/group.stamp\n"
+        "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
+        "  rustdeps = -Ldependency=obj/bar\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, Externs) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.config_values().externs().push_back(
+      std::pair("lib1", LibFile(SourceFile("//foo/lib1.rlib"))));
+  target.config_values().externs().push_back(
+      std::pair("lib2", LibFile("lib2.rlib")));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs ../../foo/lib1.rlib\n"
+        "  externs = --extern lib1=../../foo/lib1.rlib --extern "
+        "lib2=lib2.rlib\n"
+        "  rustdeps =\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, Inputs) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/source.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.config_values().inputs().push_back(SourceFile("//foo/config.json"));
+  target.config_values().inputs().push_back(SourceFile("//foo/template.h"));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "build obj/foo/bar.inputs.stamp: stamp ../../foo/config.json ../../foo/template.h\n"
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs ../../foo/config.json ../../foo/template.h "
+        "|| obj/foo/bar.inputs.stamp\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs "
+        "../../foo/config.json ../../foo/template.h\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/ninja_target_command_util.cc b/src/gn/ninja_target_command_util.cc
new file mode 100644 (file)
index 0000000..05f4ccd
--- /dev/null
@@ -0,0 +1,177 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_target_command_util.h"
+
+#include <string.h>
+
+#include "gn/c_tool.h"
+#include "gn/substitution_writer.h"
+
+namespace {
+
+// Returns the language-specific suffix for precompiled header files.
+const char* GetPCHLangSuffixForToolType(const char* name) {
+  if (name == CTool::kCToolCc)
+    return "c";
+  if (name == CTool::kCToolCxx)
+    return "cc";
+  if (name == CTool::kCToolObjC)
+    return "m";
+  if (name == CTool::kCToolObjCxx)
+    return "mm";
+  NOTREACHED() << "Not a valid PCH tool type: " << name;
+  return "";
+}
+
+}  // namespace
+
+// Returns the computed name of the Windows .pch file for the given
+// tool type. The tool must support precompiled headers.
+OutputFile GetWindowsPCHFile(const Target* target, const char* tool_name) {
+  // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up
+  // looking like "obj/chrome/browser/browser_cc.pch"
+  OutputFile ret = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ);
+  ret.value().append(target->label().name());
+  ret.value().push_back('_');
+  ret.value().append(GetPCHLangSuffixForToolType(tool_name));
+  ret.value().append(".pch");
+
+  return ret;
+}
+
+void WriteOneFlag(const Target* target,
+                  const Substitution* subst_enum,
+                  bool has_precompiled_headers,
+                  const char* tool_name,
+                  const std::vector<std::string>& (ConfigValues::*getter)()
+                      const,
+                  EscapeOptions flag_escape_options,
+                  PathOutput& path_output,
+                  std::ostream& out,
+                  bool write_substitution) {
+  if (!target->toolchain()->substitution_bits().used.count(subst_enum))
+    return;
+
+  if (write_substitution)
+    out << subst_enum->ninja_name << " =";
+
+  if (has_precompiled_headers) {
+    const CTool* tool = target->toolchain()->GetToolAsC(tool_name);
+    if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+      // Name the .pch file.
+      out << " /Fp";
+      path_output.WriteFile(out, GetWindowsPCHFile(target, tool_name));
+
+      // Enables precompiled headers and names the .h file. It's a string
+      // rather than a file name (so no need to rebase or use path_output).
+      out << " /Yu" << target->config_values().precompiled_header();
+      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
+                                           out);
+    } else if (tool && tool->precompiled_header_type() == CTool::PCH_GCC) {
+      // The targets to build the .gch files should omit the -include flag
+      // below. To accomplish this, each substitution flag is overwritten in
+      // the target rule and these values are repeated. The -include flag is
+      // omitted in place of the required -x <header lang> flag for .gch
+      // targets.
+      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
+                                           out);
+
+      // Compute the gch file (it will be language-specific).
+      std::vector<OutputFile> outputs;
+      GetPCHOutputFiles(target, tool_name, &outputs);
+      if (!outputs.empty()) {
+        // Trim the .gch suffix for the -include flag.
+        // e.g. for gch file foo/bar/target.precompiled.h.gch:
+        //          -include foo/bar/target.precompiled.h
+        std::string pch_file = outputs[0].value();
+        pch_file.erase(pch_file.length() - 4);
+        out << " -include " << pch_file;
+      }
+    } else {
+      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
+                                           out);
+    }
+  } else {
+    RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
+                                         out);
+  }
+
+  if (write_substitution)
+    out << std::endl;
+}
+
+void GetPCHOutputFiles(const Target* target,
+                       const char* tool_name,
+                       std::vector<OutputFile>* outputs) {
+  outputs->clear();
+
+  // Compute the tool. This must use the tool type passed in rather than the
+  // detected file type of the precompiled source file since the same
+  // precompiled source file will be used for separate C/C++ compiles.
+  const CTool* tool = target->toolchain()->GetToolAsC(tool_name);
+  if (!tool)
+    return;
+  SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+      target, target->config_values().precompiled_source(), tool->outputs(),
+      outputs);
+
+  if (outputs->empty())
+    return;
+  if (outputs->size() > 1)
+    outputs->resize(1);  // Only link the first output from the compiler tool.
+
+  std::string& output_value = (*outputs)[0].value();
+  size_t extension_offset = FindExtensionOffset(output_value);
+  if (extension_offset == std::string::npos) {
+    // No extension found.
+    return;
+  }
+  DCHECK(extension_offset >= 1);
+  DCHECK(output_value[extension_offset - 1] == '.');
+
+  std::string output_extension;
+  CTool::PrecompiledHeaderType header_type = tool->precompiled_header_type();
+  switch (header_type) {
+    case CTool::PCH_MSVC:
+      output_extension = GetWindowsPCHObjectExtension(
+          tool_name, output_value.substr(extension_offset - 1));
+      break;
+    case CTool::PCH_GCC:
+      output_extension = GetGCCPCHOutputExtension(tool_name);
+      break;
+    case CTool::PCH_NONE:
+      NOTREACHED() << "No outputs for no PCH type.";
+      break;
+  }
+  output_value.replace(extension_offset - 1, std::string::npos,
+                       output_extension);
+}
+
+std::string GetGCCPCHOutputExtension(const char* tool_name) {
+  const char* lang_suffix = GetPCHLangSuffixForToolType(tool_name);
+  std::string result = ".";
+  // For GCC, the output name must have a .gch suffix and be annotated with
+  // the language type. For example:
+  //   obj/foo/target_name.header.h ->
+  //   obj/foo/target_name.header.h-cc.gch
+  // In order for the compiler to pick it up, the output name (minus the .gch
+  // suffix MUST match whatever is passed to the -include flag).
+  result += "h-";
+  result += lang_suffix;
+  result += ".gch";
+  return result;
+}
+
+std::string GetWindowsPCHObjectExtension(const char* tool_name,
+                                         const std::string& obj_extension) {
+  const char* lang_suffix = GetPCHLangSuffixForToolType(tool_name);
+  std::string result = ".";
+  // For MSVC, annotate the obj files with the language type. For example:
+  //   obj/foo/target_name.precompile.obj ->
+  //   obj/foo/target_name.precompile.cc.obj
+  result += lang_suffix;
+  result += obj_extension;
+  return result;
+}
diff --git a/src/gn/ninja_target_command_util.h b/src/gn/ninja_target_command_util.h
new file mode 100644 (file)
index 0000000..d9d30bc
--- /dev/null
@@ -0,0 +1,114 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_
+#define TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_
+
+#include "base/json/string_escape.h"
+#include "gn/config_values_extractors.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/frameworks_utils.h"
+#include "gn/path_output.h"
+#include "gn/target.h"
+#include "gn/toolchain.h"
+#include "gn/variables.h"
+
+struct DefineWriter {
+  DefineWriter() { options.mode = ESCAPE_NINJA_COMMAND; }
+  DefineWriter(EscapingMode mode) { options.mode = mode; }
+
+  void operator()(const std::string& s, std::ostream& out) const {
+    out << " ";
+    EscapeStringToStream(out, "-D" + s, options);
+  }
+
+  EscapeOptions options;
+};
+
+struct FrameworkDirsWriter {
+  FrameworkDirsWriter(PathOutput& path_output, const std::string& tool_switch)
+      : path_output_(path_output), tool_switch_(tool_switch) {}
+
+  ~FrameworkDirsWriter() = default;
+
+  void operator()(const SourceDir& d, std::ostream& out) const {
+    std::ostringstream path_out;
+    path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH);
+    const std::string& path = path_out.str();
+    if (path[0] == '"')
+      out << " \"" << tool_switch_ << path.substr(1);
+    else
+      out << " " << tool_switch_ << path;
+  }
+
+  PathOutput& path_output_;
+  std::string tool_switch_;
+};
+
+struct FrameworksWriter {
+  explicit FrameworksWriter(const std::string& tool_switch)
+      : FrameworksWriter(ESCAPE_NINJA_COMMAND, tool_switch) {}
+  FrameworksWriter(EscapingMode mode, const std::string& tool_switch)
+      : tool_switch_(tool_switch) {
+    options_.mode = mode;
+  }
+
+  void operator()(const std::string& s, std::ostream& out) const {
+    out << " " << tool_switch_;
+    std::string_view framework_name = GetFrameworkName(s);
+    EscapeStringToStream(out, framework_name, options_);
+  }
+
+  EscapeOptions options_;
+  std::string tool_switch_;
+};
+
+struct IncludeWriter {
+  explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) {}
+  ~IncludeWriter() = default;
+
+  void operator()(const SourceDir& d, std::ostream& out) const {
+    std::ostringstream path_out;
+    path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH);
+    const std::string& path = path_out.str();
+    if (path[0] == '"')
+      out << " \"-I" << path.substr(1);
+    else
+      out << " -I" << path;
+  }
+
+  PathOutput& path_output_;
+};
+
+// has_precompiled_headers is set when this substitution matches a tool type
+// that supports precompiled headers, and this target supports precompiled
+// headers. It doesn't indicate if the tool has precompiled headers (this
+// will be looked up by this function).
+//
+// The tool_type indicates the corresponding tool for flags that are
+// tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g.
+// "defines") tool_type should be TYPE_NONE.
+void WriteOneFlag(const Target* target,
+                  const Substitution* subst_enum,
+                  bool has_precompiled_headers,
+                  const char* tool_name,
+                  const std::vector<std::string>& (ConfigValues::*getter)()
+                      const,
+                  EscapeOptions flag_escape_options,
+                  PathOutput& path_output,
+                  std::ostream& out,
+                  bool write_substitution = true);
+
+// Fills |outputs| with the object or gch file for the precompiled header of the
+// given type (flag type and tool type must match).
+void GetPCHOutputFiles(const Target* target,
+                       const char* tool_name,
+                       std::vector<OutputFile>* outputs);
+
+std::string GetGCCPCHOutputExtension(const char* tool_name);
+std::string GetWindowsPCHObjectExtension(const char* tool_name,
+                                         const std::string& obj_extension);
+
+#endif  // TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_
diff --git a/src/gn/ninja_target_command_util_unittest.cc b/src/gn/ninja_target_command_util_unittest.cc
new file mode 100644 (file)
index 0000000..8da62d3
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_target_command_util.h"
+
+#include <algorithm>
+#include <sstream>
+
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+// Helper function that uses a "Writer" to format a list of strings and return
+// the generated output as a string.
+template <typename Writer, typename Item>
+std::string FormatWithWriter(Writer writer, std::vector<Item> items) {
+  std::ostringstream out;
+  for (const Item& item : items) {
+    writer(item, out);
+  }
+  return out.str();
+}
+
+// Helper function running to test "Writer" by formatting N "Items" and
+// comparing the resulting string to an expected output.
+template <typename Writer, typename Item>
+void TestWriter(Writer writer,
+                std::string expected,
+                std::initializer_list<Item> items) {
+  std::string formatted =
+      FormatWithWriter(writer, std::vector<Item>(std::move(items)));
+
+  // Manually implement the check and the formatting of the error message to
+  // see the difference in the error message (by default the error message
+  // would just be "formatted == expected").
+  if (formatted != expected) {
+    std::ostringstream stream;
+    stream << '"' << expected << "\" == \"" << formatted << '"';
+    std::string message = stream.str();
+
+    ::testing::TestResult result(false, message.c_str());
+    ::testing::AssertHelper(__FILE__, __LINE__, result) = ::testing::Message();
+  }
+}
+
+}  // anonymous namespace
+
+TEST(NinjaTargetCommandUtil, DefineWriter) {
+  TestWriter(DefineWriter(),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -DFOO -DBAR=1 \"-DBAZ=\\\"Baz\\\"\"",
+#else
+             " -DFOO -DBAR=1 -DBAZ=\\\"Baz\\\"",
+#endif
+             {"FOO", "BAR=1", "BAZ=\"Baz\""});
+
+  TestWriter(DefineWriter(ESCAPE_COMPILATION_DATABASE),
+             // Escaping is different between Windows and Posix.
+             " -DFOO -DBAR=1 \"-DBAZ=\\\"Baz\\\"\"",
+             {"FOO", "BAR=1", "BAZ=\"Baz\""});
+}
+
+TEST(NinjaTargetCommandUtil, FrameworkDirsWriter) {
+  PathOutput ninja_path_output(SourceDir("//out"), "", ESCAPE_NINJA_COMMAND);
+  TestWriter(FrameworkDirsWriter(ninja_path_output, "-F"),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -F. \"-FPath$ With$ Spaces\"",
+#else
+             " -F. -FPath\\$ With\\$ Spaces",
+#endif
+             {SourceDir("//out"), SourceDir("//out/Path With Spaces")});
+
+  PathOutput space_path_output(SourceDir("//out"), "", ESCAPE_SPACE);
+  TestWriter(FrameworkDirsWriter(space_path_output, "-F"),
+             " -F. -FPath\\ With\\ Spaces",
+             {SourceDir("//out"), SourceDir("//out/Path With Spaces")});
+}
+
+TEST(NinjaTargetCommandUtil, FrameworksWriter) {
+  TestWriter(FrameworksWriter("-framework "),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -framework Foundation -framework \"Name$ With$ Spaces\"",
+#else
+             " -framework Foundation -framework Name\\$ With\\$ Spaces",
+#endif
+             {"Foundation.framework", "Name With Spaces.framework"});
+
+  TestWriter(FrameworksWriter(ESCAPE_SPACE, "-framework "),
+             " -framework Foundation -framework Name\\ With\\ Spaces",
+             {"Foundation.framework", "Name With Spaces.framework"});
+}
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
new file mode 100644 (file)
index 0000000..c8e7ebd
--- /dev/null
@@ -0,0 +1,341 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_target_writer.h"
+
+#include <sstream>
+
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "gn/config_values_extractors.h"
+#include "gn/err.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_action_target_writer.h"
+#include "gn/ninja_binary_target_writer.h"
+#include "gn/ninja_bundle_data_target_writer.h"
+#include "gn/ninja_copy_target_writer.h"
+#include "gn/ninja_create_bundle_target_writer.h"
+#include "gn/ninja_generated_file_target_writer.h"
+#include "gn/ninja_group_target_writer.h"
+#include "gn/ninja_utils.h"
+#include "gn/output_file.h"
+#include "gn/scheduler.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+
+NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
+    : settings_(target->settings()),
+      target_(target),
+      out_(out),
+      path_output_(settings_->build_settings()->build_dir(),
+                   settings_->build_settings()->root_path_utf8(),
+                   ESCAPE_NINJA) {}
+
+NinjaTargetWriter::~NinjaTargetWriter() = default;
+
+// static
+std::string NinjaTargetWriter::RunAndWriteFile(const Target* target) {
+  const Settings* settings = target->settings();
+
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE,
+                    target->label().GetUserVisibleName(false));
+  trace.SetToolchain(settings->toolchain_label());
+
+  if (g_scheduler->verbose_logging())
+    g_scheduler->Log("Computing", target->label().GetUserVisibleName(true));
+
+  // It's ridiculously faster to write to a string and then write that to
+  // disk in one operation than to use an fstream here.
+  std::stringstream rules;
+
+  // Call out to the correct sub-type of writer. Binary targets need to be
+  // written to separate files for compiler flag scoping, but other target
+  // types can have their rules coalesced.
+  //
+  // In ninja, if a rule uses a variable (like $include_dirs) it will use
+  // the value set by indenting it under the build line or it takes the value
+  // from the end of the invoking scope (otherwise the current file). It does
+  // not copy the value from what it was when the build line was encountered.
+  // To avoid writing lots of duplicate rules for defines and cflags, etc. on
+  // each source file build line, we use separate .ninja files with the shared
+  // variables set at the top.
+  //
+  // Groups and actions don't use this type of flag, they make unique rules
+  // or write variables scoped under each build line. As a result, they don't
+  // need the separate files.
+  bool needs_file_write = false;
+  if (target->output_type() == Target::BUNDLE_DATA) {
+    NinjaBundleDataTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->output_type() == Target::CREATE_BUNDLE) {
+    NinjaCreateBundleTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->output_type() == Target::COPY_FILES) {
+    NinjaCopyTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->output_type() == Target::ACTION ||
+             target->output_type() == Target::ACTION_FOREACH) {
+    NinjaActionTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->output_type() == Target::GROUP) {
+    NinjaGroupTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->output_type() == Target::GENERATED_FILE) {
+    NinjaGeneratedFileTargetWriter writer(target, rules);
+    writer.Run();
+  } else if (target->IsBinary()) {
+    needs_file_write = true;
+    NinjaBinaryTargetWriter writer(target, rules);
+    writer.Run();
+  } else {
+    CHECK(0) << "Output type of target not handled.";
+  }
+
+  if (needs_file_write) {
+    // Write the ninja file.
+    SourceFile ninja_file = GetNinjaFileForTarget(target);
+    base::FilePath full_ninja_file =
+        settings->build_settings()->GetFullPath(ninja_file);
+    base::CreateDirectory(full_ninja_file.DirName());
+    WriteFileIfChanged(full_ninja_file, rules.str(), nullptr);
+
+    EscapeOptions options;
+    options.mode = ESCAPE_NINJA;
+
+    // Return the subninja command to load the rules file.
+    std::string result = "subninja ";
+    result.append(EscapeString(
+        OutputFile(target->settings()->build_settings(), ninja_file).value(),
+        options, nullptr));
+    result.push_back('\n');
+    return result;
+  }
+
+  // No separate file required, just return the rules.
+  return rules.str();
+}
+
+void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA;
+
+  out_ << type->ninja_name << " = ";
+  EscapeStringToStream(
+      out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts);
+  out_ << std::endl;
+}
+
+void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) {
+  bool written_anything = false;
+
+  // Target label.
+  if (bits.used.count(&SubstitutionLabel)) {
+    WriteEscapedSubstitution(&SubstitutionLabel);
+    written_anything = true;
+  }
+
+  // Target label name.
+  if (bits.used.count(&SubstitutionLabelName)) {
+    WriteEscapedSubstitution(&SubstitutionLabelName);
+    written_anything = true;
+  }
+
+  // Target label name without toolchain.
+  if (bits.used.count(&SubstitutionLabelNoToolchain)) {
+    WriteEscapedSubstitution(&SubstitutionLabelNoToolchain);
+    written_anything = true;
+  }
+
+  // Root gen dir.
+  if (bits.used.count(&SubstitutionRootGenDir)) {
+    WriteEscapedSubstitution(&SubstitutionRootGenDir);
+    written_anything = true;
+  }
+
+  // Root out dir.
+  if (bits.used.count(&SubstitutionRootOutDir)) {
+    WriteEscapedSubstitution(&SubstitutionRootOutDir);
+    written_anything = true;
+  }
+
+  // Target gen dir.
+  if (bits.used.count(&SubstitutionTargetGenDir)) {
+    WriteEscapedSubstitution(&SubstitutionTargetGenDir);
+    written_anything = true;
+  }
+
+  // Target out dir.
+  if (bits.used.count(&SubstitutionTargetOutDir)) {
+    WriteEscapedSubstitution(&SubstitutionTargetOutDir);
+    written_anything = true;
+  }
+
+  // Target output name.
+  if (bits.used.count(&SubstitutionTargetOutputName)) {
+    WriteEscapedSubstitution(&SubstitutionTargetOutputName);
+    written_anything = true;
+  }
+
+  // If we wrote any vars, separate them from the rest of the file that follows
+  // with a blank line.
+  if (written_anything)
+    out_ << std::endl;
+}
+
+std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
+    const std::vector<const Target*>& additional_hard_deps,
+    size_t num_stamp_uses) const {
+  CHECK(target_->toolchain()) << "Toolchain not set on target "
+                              << target_->label().GetUserVisibleName(true);
+
+  // ----------
+  // Collect all input files that are input deps of this target. Knowing the
+  // number before writing allows us to either skip writing the input deps
+  // stamp or optimize it. Use pointers to avoid copies here.
+  std::vector<const SourceFile*> input_deps_sources;
+  input_deps_sources.reserve(32);
+
+  // Actions get implicit dependencies on the script itself.
+  if (target_->output_type() == Target::ACTION ||
+      target_->output_type() == Target::ACTION_FOREACH)
+    input_deps_sources.push_back(&target_->action_values().script());
+
+  // Input files are only considered for non-binary targets which use an
+  // implicit dependency instead. The implicit dependency in this case is
+  // handled separately by the binary target writer.
+  if (!target_->IsBinary()) {
+    for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+      for (const auto& input : iter.cur().inputs())
+        input_deps_sources.push_back(&input);
+    }
+  }
+
+  // For an action (where we run a script only once) the sources are the same
+  // as the inputs. For action_foreach, the sources will be operated on
+  // separately so don't handle them here.
+  if (target_->output_type() == Target::ACTION) {
+    for (const auto& source : target_->sources())
+      input_deps_sources.push_back(&source);
+  }
+
+  // ----------
+  // Collect all target input dependencies of this target as was done for the
+  // files above.
+  std::vector<const Target*> input_deps_targets;
+  input_deps_targets.reserve(32);
+
+  // Hard dependencies that are direct or indirect dependencies.
+  // These are large (up to 100s), hence why we check other
+  const std::set<const Target*>& hard_deps(target_->recursive_hard_deps());
+  for (const Target* target : hard_deps) {
+    // BUNDLE_DATA should normally be treated as a data-only dependency
+    // (see Target::IsDataOnly()). Only the CREATE_BUNDLE target, that actually
+    // consumes this data, needs to have the BUNDLE_DATA as an input dependency.
+    if (target->output_type() != Target::BUNDLE_DATA ||
+        target_->output_type() == Target::CREATE_BUNDLE)
+      input_deps_targets.push_back(target);
+  }
+
+  // Additional hard dependencies passed in. These are usually empty or small,
+  // and we don't want to duplicate the explicit hard deps of the target.
+  for (const Target* target : additional_hard_deps) {
+    if (!hard_deps.count(target))
+      input_deps_targets.push_back(target);
+  }
+
+  // Toolchain dependencies. These must be resolved before doing anything.
+  // This just writes all toolchain deps for simplicity. If we find that
+  // toolchains often have more than one dependency, we could consider writing
+  // a toolchain-specific stamp file and only include the stamp here.
+  // Note that these are usually empty/small.
+  const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
+  for (const auto& toolchain_dep : toolchain_deps) {
+    // This could theoretically duplicate dependencies already in the list,
+    // but it shouldn't happen in practice, is inconvenient to check for,
+    // and only results in harmless redundant dependencies listed.
+    input_deps_targets.push_back(toolchain_dep.ptr);
+  }
+
+  // ---------
+  // Write the outputs.
+
+  if (input_deps_sources.size() + input_deps_targets.size() == 0)
+    return std::vector<OutputFile>();  // No input dependencies.
+
+  // If we're only generating one input dependency, return it directly instead
+  // of writing a stamp file for it.
+  if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
+    return std::vector<OutputFile>{
+        OutputFile(settings_->build_settings(), *input_deps_sources[0])};
+  if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) {
+    const OutputFile& dep = input_deps_targets[0]->dependency_output_file();
+    DCHECK(!dep.value().empty());
+    return std::vector<OutputFile>{dep};
+  }
+
+  std::vector<OutputFile> outs;
+  // File input deps.
+  for (const SourceFile* source : input_deps_sources)
+    outs.push_back(OutputFile(settings_->build_settings(), *source));
+  // Target input deps. Sort by label so the output is deterministic (otherwise
+  // some of the targets will have gone through std::sets which will have
+  // sorted them by pointer).
+  std::sort(
+      input_deps_targets.begin(), input_deps_targets.end(),
+      [](const Target* a, const Target* b) { return a->label() < b->label(); });
+  for (auto* dep : input_deps_targets) {
+    DCHECK(!dep->dependency_output_file().value().empty());
+    outs.push_back(dep->dependency_output_file());
+  }
+
+  // If there are multiple inputs, but the stamp file would be referenced only
+  // once, don't write it but depend on the inputs directly.
+  if (num_stamp_uses == 1u)
+    return outs;
+
+  // Make a stamp file.
+  OutputFile input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  input_stamp_file.value().append(target_->label().name());
+  input_stamp_file.value().append(".inputdeps.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+  path_output_.WriteFiles(out_, outs);
+
+  out_ << "\n";
+  return std::vector<OutputFile>{input_stamp_file};
+}
+
+void NinjaTargetWriter::WriteStampForTarget(
+    const std::vector<OutputFile>& files,
+    const std::vector<OutputFile>& order_only_deps) {
+  const OutputFile& stamp_file = target_->dependency_output_file();
+
+  // First validate that the target's dependency is a stamp file. Otherwise,
+  // we shouldn't have gotten here!
+  CHECK(base::EndsWith(stamp_file.value(), ".stamp",
+                       base::CompareCase::INSENSITIVE_ASCII))
+      << "Output should end in \".stamp\" for stamp file output. Instead got: "
+      << "\"" << stamp_file.value() << "\"";
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, stamp_file);
+
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+  path_output_.WriteFiles(out_, files);
+
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
+  }
+  out_ << std::endl;
+}
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h
new file mode 100644 (file)
index 0000000..efcf73d
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_TARGET_WRITER_H_
+
+#include <iosfwd>
+
+#include "base/macros.h"
+#include "gn/path_output.h"
+#include "gn/substitution_type.h"
+
+class OutputFile;
+class Settings;
+class Target;
+struct SubstitutionBits;
+
+// Generates one target's ".ninja" file. The toplevel "build.ninja" file is
+// generated by the NinjaBuildWriter.
+class NinjaTargetWriter {
+ public:
+  NinjaTargetWriter(const Target* target, std::ostream& out);
+  virtual ~NinjaTargetWriter();
+
+  // Returns the build line to be written to the toolchain build file.
+  //
+  // Some targets have their rules written to separate files, and some can have
+  // their rules coalesced in the main build file. For the coalesced case, this
+  // function will return the rules as a string. For the separate file case,
+  // the separate ninja file will be written and the return string will be the
+  // subninja command to load that file.
+  static std::string RunAndWriteFile(const Target* target);
+
+  virtual void Run() = 0;
+
+ protected:
+  // Writes out the substitution values that are shared between the different
+  // types of tools (target gen dir, target label, etc.). Only the substitutions
+  // identified by the given bits will be written.
+  void WriteSharedVars(const SubstitutionBits& bits);
+
+  // Writes to the output stream a stamp rule for input dependencies, and
+  // returns the file to be appended to source rules that encodes the
+  // order-only dependencies for the current target.
+  // If num_stamp_uses is small, this might return all input dependencies
+  // directly, without writing a stamp file.
+  // If there are no implicit dependencies and no additional target dependencies
+  // are passed in, this returns an empty vector.
+  std::vector<OutputFile> WriteInputDepsStampAndGetDep(
+      const std::vector<const Target*>& additional_hard_deps,
+      size_t num_stamp_uses) const;
+
+  // Writes to the output file a final stamp rule for the target that stamps
+  // the given list of files. This function assumes the stamp is for the target
+  // as a whole so the stamp file is set as the target's dependency output.
+  void WriteStampForTarget(const std::vector<OutputFile>& deps,
+                           const std::vector<OutputFile>& order_only_deps);
+
+  const Settings* settings_;  // Non-owning.
+  const Target* target_;      // Non-owning.
+  std::ostream& out_;
+  PathOutput path_output_;
+
+ private:
+  void WriteCopyRules();
+  void WriteEscapedSubstitution(const Substitution* type);
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_TARGET_WRITER_H_
diff --git a/src/gn/ninja_target_writer_unittest.cc b/src/gn/ninja_target_writer_unittest.cc
new file mode 100644 (file)
index 0000000..7a04e3c
--- /dev/null
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "gn/ninja_action_target_writer.h"
+#include "gn/ninja_target_writer.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+class TestingNinjaTargetWriter : public NinjaTargetWriter {
+ public:
+  TestingNinjaTargetWriter(const Target* target,
+                           const Toolchain* toolchain,
+                           std::ostream& out)
+      : NinjaTargetWriter(target, out) {}
+
+  void Run() override {}
+
+  // Make this public so the test can call it.
+  std::vector<OutputFile> WriteInputDepsStampAndGetDep(
+      const std::vector<const Target*>& additional_hard_deps,
+      size_t num_stamp_uses) {
+    return NinjaTargetWriter::WriteInputDepsStampAndGetDep(additional_hard_deps,
+                                                           num_stamp_uses);
+  }
+};
+
+}  // namespace
+
+TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) {
+  TestWithScope setup;
+  Err err;
+
+  // Make a base target that's a hard dep (action).
+  Target base_target(setup.settings(), Label(SourceDir("//foo/"), "base"));
+  base_target.set_output_type(Target::ACTION);
+  base_target.visibility().SetPublic();
+  base_target.SetToolchain(setup.toolchain());
+  base_target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  // Dependent target that also includes a source prerequisite (should get
+  // included) and a source (should not be included).
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "target"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  target.SetToolchain(setup.toolchain());
+  target.config_values().inputs().push_back(SourceFile("//foo/input.txt"));
+  target.sources().push_back(SourceFile("//foo/source.txt"));
+  target.public_deps().push_back(LabelTargetPair(&base_target));
+
+  // Dependent action to test that action sources will be treated the same as
+  // inputs.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
+  action.set_output_type(Target::ACTION);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  action.action_values().set_script(SourceFile("//foo/script.py"));
+  action.sources().push_back(SourceFile("//foo/action_source.txt"));
+  action.public_deps().push_back(LabelTargetPair(&target));
+
+  ASSERT_TRUE(base_target.OnResolved(&err));
+  ASSERT_TRUE(target.OnResolved(&err));
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // Input deps for the base (should be only the script itself).
+  {
+    std::ostringstream stream;
+    TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream);
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
+
+    // Since there is only one dependency, it should just be returned and
+    // nothing written to the stream.
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("../../foo/script.py", dep[0].value());
+    EXPECT_EQ("", stream.str());
+  }
+
+  // Input deps for the target (should depend on the base).
+  {
+    std::ostringstream stream;
+    TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
+
+    // Since there is only one dependency, a stamp file will be returned
+    // directly without writing any additional rules.
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("obj/foo/base.stamp", dep[0].value());
+  }
+
+  {
+    std::ostringstream stream;
+    NinjaActionTargetWriter writer(&action, stream);
+    writer.Run();
+    EXPECT_EQ(
+        "rule __foo_action___rule\n"
+        "  command =  ../../foo/script.py\n"
+        "  description = ACTION //foo:action()\n"
+        "  restat = 1\n"
+        "\n"
+        "build: __foo_action___rule | ../../foo/script.py"
+        " ../../foo/action_source.txt ./target\n"
+        "\n"
+        "build obj/foo/action.stamp: stamp\n",
+        stream.str());
+  }
+
+  // Input deps for action which should depend on the base since its a hard dep
+  // that is a (indirect) dependency, as well as the the action source.
+  {
+    std::ostringstream stream;
+    TestingNinjaTargetWriter writer(&action, setup.toolchain(), stream);
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
+
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("obj/foo/action.inputdeps.stamp", dep[0].value());
+    EXPECT_EQ(
+        "build obj/foo/action.inputdeps.stamp: stamp ../../foo/script.py "
+        "../../foo/action_source.txt ./target\n",
+        stream.str());
+  }
+}
+
+// Tests WriteInputDepsStampAndGetDep when toolchain deps are present.
+TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // Toolchain dependency. Here we make a target in the same toolchain for
+  // simplicity, but in real life (using the Builder) this would be rejected
+  // because it would be a circular dependency (the target depends on its
+  // toolchain, and the toolchain depends on this target).
+  Target toolchain_dep_target(setup.settings(),
+                              Label(SourceDir("//foo/"), "setup"));
+  toolchain_dep_target.set_output_type(Target::ACTION);
+  toolchain_dep_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(toolchain_dep_target.OnResolved(&err));
+  setup.toolchain()->deps().push_back(LabelTargetPair(&toolchain_dep_target));
+
+  // Make a binary target
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "target"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
+  std::vector<OutputFile> dep =
+      writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
+
+  // Since there is more than one dependency, a stamp file will be returned
+  // and the rule for the stamp file will be written to the stream.
+  ASSERT_EQ(1u, dep.size());
+  EXPECT_EQ("obj/foo/setup.stamp", dep[0].value());
+  EXPECT_EQ("", stream.str());
+}
diff --git a/src/gn/ninja_toolchain_writer.cc b/src/gn/ninja_toolchain_writer.cc
new file mode 100644 (file)
index 0000000..7be7719
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_toolchain_writer.h"
+
+#include <fstream>
+
+#include "base/files/file_util.h"
+#include "base/strings/stringize_macros.h"
+#include "gn/build_settings.h"
+#include "gn/c_tool.h"
+#include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
+#include "gn/ninja_utils.h"
+#include "gn/pool.h"
+#include "gn/settings.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/toolchain.h"
+#include "gn/trace.h"
+
+namespace {
+
+const char kIndent[] = "  ";
+
+}  // namespace
+
+NinjaToolchainWriter::NinjaToolchainWriter(const Settings* settings,
+                                           const Toolchain* toolchain,
+                                           std::ostream& out)
+    : settings_(settings),
+      toolchain_(toolchain),
+      out_(out),
+      path_output_(settings_->build_settings()->build_dir(),
+                   settings_->build_settings()->root_path_utf8(),
+                   ESCAPE_NINJA) {}
+
+NinjaToolchainWriter::~NinjaToolchainWriter() = default;
+
+void NinjaToolchainWriter::Run(
+    const std::vector<NinjaWriter::TargetRulePair>& rules) {
+  std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_);
+
+  for (const auto& tool : toolchain_->tools()) {
+    if (tool.second->name() == GeneralTool::kGeneralToolAction)
+      continue;
+    WriteToolRule(tool.second.get(), rule_prefix);
+  }
+  out_ << std::endl;
+
+  for (const auto& pair : rules)
+    out_ << pair.second;
+}
+
+// static
+bool NinjaToolchainWriter::RunAndWriteFile(
+    const Settings* settings,
+    const Toolchain* toolchain,
+    const std::vector<NinjaWriter::TargetRulePair>& rules) {
+  base::FilePath ninja_file(settings->build_settings()->GetFullPath(
+      GetNinjaFileForToolchain(settings)));
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, FilePathToUTF8(ninja_file));
+
+  base::CreateDirectory(ninja_file.DirName());
+
+  std::ofstream file;
+  file.open(FilePathToUTF8(ninja_file).c_str(),
+            std::ios_base::out | std::ios_base::binary);
+  if (file.fail())
+    return false;
+
+  NinjaToolchainWriter gen(settings, toolchain, file);
+  gen.Run(rules);
+  return true;
+}
+
+void NinjaToolchainWriter::WriteToolRule(Tool* tool,
+                                         const std::string& rule_prefix) {
+  out_ << "rule " << rule_prefix << tool->name() << std::endl;
+
+  // Rules explicitly include shell commands, so don't try to escape.
+  EscapeOptions options;
+  options.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+
+  WriteCommandRulePattern("command", tool->command_launcher(), tool->command(),
+                          options);
+
+  WriteRulePattern("description", tool->description(), options);
+  WriteRulePattern("rspfile", tool->rspfile(), options);
+  WriteRulePattern("rspfile_content", tool->rspfile_content(), options);
+
+  if (CTool* c_tool = tool->AsC()) {
+    if (c_tool->depsformat() == CTool::DEPS_GCC) {
+      // GCC-style deps require a depfile.
+      if (!c_tool->depfile().empty()) {
+        WriteRulePattern("depfile", tool->depfile(), options);
+        out_ << kIndent << "deps = gcc" << std::endl;
+      }
+    } else if (c_tool->depsformat() == CTool::DEPS_MSVC) {
+      // MSVC deps don't have a depfile.
+      out_ << kIndent << "deps = msvc" << std::endl;
+    }
+  } else if (!tool->depfile().empty()) {
+    WriteRulePattern("depfile", tool->depfile(), options);
+    out_ << kIndent << "deps = gcc" << std::endl;
+  }
+
+  // Use pool is specified.
+  if (tool->pool().ptr) {
+    std::string pool_name =
+        tool->pool().ptr->GetNinjaName(settings_->default_toolchain_label());
+    out_ << kIndent << "pool = " << pool_name << std::endl;
+  }
+
+  if (tool->restat())
+    out_ << kIndent << "restat = 1" << std::endl;
+}
+
+void NinjaToolchainWriter::WriteRulePattern(const char* name,
+                                            const SubstitutionPattern& pattern,
+                                            const EscapeOptions& options) {
+  if (pattern.empty())
+    return;
+  out_ << kIndent << name << " = ";
+  SubstitutionWriter::WriteWithNinjaVariables(pattern, options, out_);
+  out_ << std::endl;
+}
+
+void NinjaToolchainWriter::WriteCommandRulePattern(
+    const char* name,
+    const std::string& launcher,
+    const SubstitutionPattern& command,
+    const EscapeOptions& options) {
+  CHECK(!command.empty()) << "Command should not be empty";
+  out_ << kIndent << name << " = " ;
+  if (!launcher.empty())
+    out_ << launcher << " ";
+  SubstitutionWriter::WriteWithNinjaVariables(command, options, out_);
+  out_ << std::endl;
+}
diff --git a/src/gn/ninja_toolchain_writer.h b/src/gn/ninja_toolchain_writer.h
new file mode 100644 (file)
index 0000000..97ec481
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_
+#define TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_
+
+#include <iosfwd>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "gn/ninja_writer.h"
+#include "gn/path_output.h"
+#include "gn/toolchain.h"
+
+struct EscapeOptions;
+class Settings;
+class Tool;
+
+class NinjaToolchainWriter {
+ public:
+  // Takes the settings for the toolchain, as well as the list of all targets
+  // associated with the toolchain.
+  static bool RunAndWriteFile(
+      const Settings* settings,
+      const Toolchain* toolchain,
+      const std::vector<NinjaWriter::TargetRulePair>& rules);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRule);
+  FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRuleWithLauncher);
+
+  NinjaToolchainWriter(const Settings* settings,
+                       const Toolchain* toolchain,
+                       std::ostream& out);
+  ~NinjaToolchainWriter();
+
+  void Run(const std::vector<NinjaWriter::TargetRulePair>& extra_rules);
+
+  void WriteRules();
+  void WriteToolRule(Tool* tool, const std::string& rule_prefix);
+  void WriteRulePattern(const char* name,
+                        const SubstitutionPattern& pattern,
+                        const EscapeOptions& options);
+  void WriteCommandRulePattern(const char* name,
+                               const std::string& launcher,
+                               const SubstitutionPattern& command,
+                               const EscapeOptions& options);
+
+  const Settings* settings_;
+  const Toolchain* toolchain_;
+  std::ostream& out_;
+  PathOutput path_output_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaToolchainWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_
diff --git a/src/gn/ninja_toolchain_writer_unittest.cc b/src/gn/ninja_toolchain_writer_unittest.cc
new file mode 100644 (file)
index 0000000..12eeb00
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "gn/ninja_toolchain_writer.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(NinjaToolchainWriter, WriteToolRule) {
+  TestWithScope setup;
+
+  std::ostringstream stream;
+  NinjaToolchainWriter writer(setup.settings(), setup.toolchain(), stream);
+  writer.WriteToolRule(setup.toolchain()->GetTool(CTool::kCToolCc),
+                       std::string("prefix_"));
+
+  EXPECT_EQ(
+      "rule prefix_cc\n"
+      "  command = cc ${in} ${cflags} ${cflags_c} ${defines} ${include_dirs} "
+      "-o ${out}\n",
+      stream.str());
+}
+
+TEST(NinjaToolchainWriter, WriteToolRuleWithLauncher) {
+  TestWithScope setup;
+
+  std::ostringstream stream;
+  NinjaToolchainWriter writer(setup.settings(), setup.toolchain(), stream);
+  writer.WriteToolRule(setup.toolchain()->GetTool(CTool::kCToolCxx),
+                       std::string("prefix_"));
+
+  EXPECT_EQ(
+      "rule prefix_cxx\n"
+      "  command = launcher c++ ${in} ${cflags} ${cflags_cc} ${defines} ${include_dirs} "
+      "-o ${out}\n",
+      stream.str());
+}
diff --git a/src/gn/ninja_tools.cc b/src/gn/ninja_tools.cc
new file mode 100644 (file)
index 0000000..2eaf31c
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_tools.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/err.h"
+#include "gn/exec_process.h"
+#include "gn/filesystem_utils.h"
+
+namespace {
+
+base::CommandLine CreateNinjaToolCommandLine(const base::FilePath& ninja_executable,
+                                             const std::string& tool) {
+  base::CommandLine cmdline(ninja_executable);
+  cmdline.SetParseSwitches(false);
+  cmdline.AppendArg("-t");
+  cmdline.AppendArg(tool);
+  return cmdline;
+}
+
+bool RunNinja(const base::CommandLine& cmdline,
+              const base::FilePath& startup_dir,
+              std::string* output,
+              Err* err) {
+  std::string stderr_output;
+
+  int exit_code = 0;
+  if (!internal::ExecProcess(cmdline, startup_dir, output, &stderr_output,
+                             &exit_code)) {
+    *err = Err(Location(), "Could not execute Ninja.",
+               "I was trying to execute \"" +
+                   FilePathToUTF8(cmdline.GetProgram()) + "\".");
+    return false;
+  }
+
+  if (exit_code != 0) {
+    *err = Err(Location(), "Ninja has quit with exit code " +
+                               base::IntToString(exit_code) + ".");
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+bool InvokeNinjaRestatTool(const base::FilePath& ninja_executable,
+                           const base::FilePath& build_dir,
+                           const std::vector<base::FilePath>& files_to_restat,
+                           Err* err) {
+  base::CommandLine cmdline =
+      CreateNinjaToolCommandLine(ninja_executable, "restat");
+  for (const base::FilePath& file : files_to_restat) {
+    cmdline.AppendArgPath(file);
+  }
+  std::string output;
+  return RunNinja(cmdline, build_dir, &output, err);
+}
+
+bool InvokeNinjaCleanDeadTool(const base::FilePath& ninja_executable,
+                              const base::FilePath& build_dir,
+                              Err* err) {
+  base::CommandLine cmdline =
+      CreateNinjaToolCommandLine(ninja_executable, "cleandead");
+  std::string output;
+  return RunNinja(cmdline, build_dir, &output, err);
+}
+
+bool InvokeNinjaRecompactTool(const base::FilePath& ninja_executable,
+                              const base::FilePath& build_dir,
+                              Err* err) {
+  base::CommandLine cmdline =
+      CreateNinjaToolCommandLine(ninja_executable, "recompact");
+  std::string output;
+  return RunNinja(cmdline, build_dir, &output, err);
+}
diff --git a/src/gn/ninja_tools.h b/src/gn/ninja_tools.h
new file mode 100644 (file)
index 0000000..9721296
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_TOOLS_H_
+#define TOOLS_GN_NINJA_TOOLS_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "gn/err.h"
+
+// Invokes the ninja restat tool (ie, ninja -C build_dir -t restat). This tool
+// tells ninja that it should check the mtime of the provided files and update
+// the .ninja_log accordingly. This is useful when GN knows that an output file
+// in the ninja graph has been updated without invoking ninja.
+//
+// The best example of this is after gn gen runs, we know that build.ninja has
+// been potentially updated, but ninja will still use the mtime from the
+// .ninja_log and could trigger another re-gen. By telling ninja to restat
+// build.ninja, we can eliminate the extra re-gen.
+//
+// If files_to_restat is empty, ninja will restat all files that have an entry
+// in the .ninja_log.
+bool InvokeNinjaRestatTool(const base::FilePath& ninja_executable,
+                           const base::FilePath& build_dir,
+                           const std::vector<base::FilePath>& files_to_restat,
+                           Err* err);
+
+// Invokes the ninja cleandead tool (ie, ninja -C build_dir -t cleandead). This
+// tool removes files produced by previous builds that are no longer in the
+// build file.
+bool InvokeNinjaCleanDeadTool(const base::FilePath& ninja_executable,
+                              const base::FilePath& build_dir,
+                              Err* err);
+
+// Invokes the ninja recompact tool (ie, ninja -C build_dir -t recompact). This
+// tool prunes the .ninja_log and .ninja_deps entries that are no longer part of
+// the build graph.
+bool InvokeNinjaRecompactTool(const base::FilePath& ninja_executable,
+                              const base::FilePath& build_dir,
+                              Err* err);
+
+#endif // TOOLS_GN_NINJA_TOOLS_H_
diff --git a/src/gn/ninja_utils.cc b/src/gn/ninja_utils.cc
new file mode 100644 (file)
index 0000000..95cf3a1
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_utils.h"
+
+#include "gn/filesystem_utils.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+
+SourceFile GetNinjaFileForTarget(const Target* target) {
+  return SourceFile(
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ).value() +
+      target->label().name() + ".ninja");
+}
+
+SourceFile GetNinjaFileForToolchain(const Settings* settings) {
+  return SourceFile(GetBuildDirAsSourceDir(BuildDirContext(settings),
+                                           BuildDirType::TOOLCHAIN_ROOT)
+                        .value() +
+                    "toolchain.ninja");
+}
+
+std::string GetNinjaRulePrefixForToolchain(const Settings* settings) {
+  // Don't prefix the default toolchain so it looks prettier, prefix everything
+  // else.
+  if (settings->is_default())
+    return std::string();  // Default toolchain has no prefix.
+  return settings->toolchain_label().name() + "_";
+}
diff --git a/src/gn/ninja_utils.h b/src/gn/ninja_utils.h
new file mode 100644 (file)
index 0000000..60ae6b2
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_UTILS_H_
+#define TOOLS_GN_NINJA_UTILS_H_
+
+#include <string>
+
+class Settings;
+class SourceFile;
+class Target;
+
+// Example: "base/base.ninja". The string version will not be escaped, and
+// will always have slashes for path separators.
+SourceFile GetNinjaFileForTarget(const Target* target);
+
+// Returns the name of the root .ninja file for the given toolchain.
+SourceFile GetNinjaFileForToolchain(const Settings* settings);
+
+// Returns the prefix applied to the Ninja rules in a given toolchain so they
+// don't collide with rules from other toolchains.
+std::string GetNinjaRulePrefixForToolchain(const Settings* settings);
+
+#endif  // TOOLS_GN_NINJA_UTILS_H_
diff --git a/src/gn/ninja_writer.cc b/src/gn/ninja_writer.cc
new file mode 100644 (file)
index 0000000..999718a
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_writer.h"
+
+#include "gn/builder.h"
+#include "gn/loader.h"
+#include "gn/location.h"
+#include "gn/ninja_build_writer.h"
+#include "gn/ninja_toolchain_writer.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+
+NinjaWriter::NinjaWriter(const Builder& builder) : builder_(builder) {}
+
+NinjaWriter::~NinjaWriter() = default;
+
+// static
+bool NinjaWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+                                   const Builder& builder,
+                                   const PerToolchainRules& per_toolchain_rules,
+                                   Err* err) {
+  NinjaWriter writer(builder);
+
+  if (!writer.WriteToolchains(per_toolchain_rules, err))
+    return false;
+  return NinjaBuildWriter::RunAndWriteFile(build_settings, builder, err);
+}
+
+bool NinjaWriter::WriteToolchains(const PerToolchainRules& per_toolchain_rules,
+                                  Err* err) {
+  if (per_toolchain_rules.empty()) {
+    *err = Err(Location(), "No targets.",
+        "I could not find any targets to write, so I'm doing nothing.");
+    return false;
+  }
+
+  for (const auto& i : per_toolchain_rules) {
+    const Toolchain* toolchain = i.first;
+    const Settings* settings =
+        builder_.loader()->GetToolchainSettings(toolchain->label());
+    if (!NinjaToolchainWriter::RunAndWriteFile(settings, toolchain, i.second)) {
+      *err =
+          Err(Location(), "Couldn't open toolchain buildfile(s) for writing");
+      return false;
+    }
+  }
+
+  return true;
+}
diff --git a/src/gn/ninja_writer.h b/src/gn/ninja_writer.h
new file mode 100644 (file)
index 0000000..b9b93a4
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_WRITER_H_
+#define TOOLS_GN_NINJA_WRITER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class Builder;
+class BuildSettings;
+class Err;
+class Target;
+class Toolchain;
+
+class NinjaWriter {
+ public:
+  // Combines a target and the computed build rule for it.
+  using TargetRulePair = std::pair<const Target*, std::string>;
+
+  // Associates the build rules with each toolchain.
+  using PerToolchainRules =
+      std::map<const Toolchain*, std::vector<TargetRulePair>>;
+
+  // On failure will populate |err| and will return false.  The map contains
+  // the per-toolchain set of rules collected to write to the toolchain build
+  // files.
+  static bool RunAndWriteFiles(const BuildSettings* build_settings,
+                               const Builder& builder,
+                               const PerToolchainRules& per_toolchain_rules,
+                               Err* err);
+
+ private:
+  NinjaWriter(const Builder& builder);
+  ~NinjaWriter();
+
+  bool WriteToolchains(const PerToolchainRules& per_toolchain_rules, Err* err);
+
+  const Builder& builder_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_WRITER_H_
diff --git a/src/gn/operators.cc b/src/gn/operators.cc
new file mode 100644 (file)
index 0000000..7c04dd3
--- /dev/null
@@ -0,0 +1,744 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/operators.h"
+
+#include <stddef.h>
+#include <algorithm>
+
+#include "base/strings/string_number_conversions.h"
+#include "gn/err.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/token.h"
+#include "gn/value.h"
+
+namespace {
+
+// Helper class used for assignment operations: =, +=, and -= to generalize
+// writing to various types of destinations.
+class ValueDestination {
+ public:
+  ValueDestination();
+
+  bool Init(Scope* exec_scope,
+            const ParseNode* dest,
+            const BinaryOpNode* op_node,
+            Err* err);
+
+  // Returns the value in the destination scope if it already exists, or null
+  // if it doesn't. This is for validation and does not count as a "use".
+  // Other nested scopes will be searched.
+  const Value* GetExistingValue() const;
+
+  // Returns an existing version of the output if it can be modified. This will
+  // not search nested scopes since writes only go into the current scope.
+  // Returns null if the value does not exist, or is not in the current scope
+  // (meaning assignments won't go to this value and it's not mutable). This
+  // is for implementing += and -=.
+  //
+  // If it exists, this will mark the origin of the value to be the passed-in
+  // node, and the value will be also marked unused (if possible) under the
+  // assumption that it will be modified in-place.
+  Value* GetExistingMutableValueIfExists(const ParseNode* origin);
+
+  // Returns a pointer to the value that was set.
+  Value* SetValue(Value value, const ParseNode* set_node);
+
+  // Fills the Err with an undefined value error appropriate for modification
+  // operators: += and -= (where the source is also the dest).
+  void MakeUndefinedIdentifierForModifyError(Err* err);
+
+ private:
+  enum Type { UNINITIALIZED, SCOPE, LIST };
+
+  Type type_;
+
+  // Valid when type_ == SCOPE.
+  Scope* scope_;
+  const Token* name_token_;
+
+  // Valid when type_ == LIST.
+  Value* list_;
+  size_t index_;  // Guaranteed in-range when Init() succeeds.
+};
+
+ValueDestination::ValueDestination()
+    : type_(UNINITIALIZED),
+      scope_(nullptr),
+      name_token_(nullptr),
+      list_(nullptr),
+      index_(0) {}
+
+bool ValueDestination::Init(Scope* exec_scope,
+                            const ParseNode* dest,
+                            const BinaryOpNode* op_node,
+                            Err* err) {
+  // Check for standard variable set.
+  const IdentifierNode* dest_identifier = dest->AsIdentifier();
+  if (dest_identifier) {
+    type_ = SCOPE;
+    scope_ = exec_scope;
+    name_token_ = &dest_identifier->value();
+    return true;
+  }
+
+  // Check for array and scope accesses. The base (array or scope variable
+  // name) must always be defined ahead of time.
+  const AccessorNode* dest_accessor = dest->AsAccessor();
+  if (!dest_accessor) {
+    *err = Err(op_node, "Assignment requires a lvalue.",
+               "This thing on the left is not an identifier or accessor.");
+    err->AppendRange(dest->GetRange());
+    return false;
+  }
+
+  // Known to be an accessor.
+  std::string_view base_str = dest_accessor->base().value();
+  Value* base =
+      exec_scope->GetMutableValue(base_str, Scope::SEARCH_CURRENT, false);
+  if (!base) {
+    // Base is either undefined or it's defined but not in the current scope.
+    // Make a good error message.
+    if (exec_scope->GetValue(base_str, false)) {
+      *err = Err(
+          dest_accessor->base(), "Suspicious in-place modification.",
+          "This variable exists in a containing scope. Normally, writing to it "
+          "would\nmake a copy of it into the current scope with the modified "
+          "version. But\nhere you're modifying only an element of a scope or "
+          "list object. It's unlikely\nyou meant to copy the entire thing just "
+          "to modify this part of it.\n"
+          "\n"
+          "If you really wanted to do this, do:\n"
+          "  " +
+              std::string(base_str) + " = " + std::string(base_str) +
+              "\n"
+              "to copy it into the current scope before doing this operation.");
+    } else {
+      *err = Err(dest_accessor->base(), "Undefined identifier.");
+    }
+    return false;
+  }
+
+  if (dest_accessor->subscript()) {
+    // List access with an index.
+    if (!base->VerifyTypeIs(Value::LIST, err)) {
+      // Errors here will confusingly refer to the variable declaration (since
+      // that's all Value knows) rather than the list access. So rewrite the
+      // error location to refer to the base value's location.
+      *err = Err(dest_accessor->base(), err->message(), err->help_text());
+      return false;
+    }
+
+    type_ = LIST;
+    list_ = base;
+    return dest_accessor->ComputeAndValidateListIndex(
+        exec_scope, base->list_value().size(), &index_, err);
+  }
+
+  // Scope access with a dot.
+  if (!base->VerifyTypeIs(Value::SCOPE, err)) {
+    // As the for the list index case above, rewrite the error location.
+    *err = Err(dest_accessor->base(), err->message(), err->help_text());
+    return false;
+  }
+  type_ = SCOPE;
+  scope_ = base->scope_value();
+  name_token_ = &dest_accessor->member()->value();
+  return true;
+}
+
+const Value* ValueDestination::GetExistingValue() const {
+  if (type_ == SCOPE)
+    return scope_->GetValue(name_token_->value(), true);
+  else if (type_ == LIST)
+    return &list_->list_value()[index_];
+  return nullptr;
+}
+
+Value* ValueDestination::GetExistingMutableValueIfExists(
+    const ParseNode* origin) {
+  if (type_ == SCOPE) {
+    Value* value = scope_->GetMutableValue(name_token_->value(),
+                                           Scope::SEARCH_CURRENT, false);
+    if (value) {
+      // The value will be written to, reset its tracking information.
+      value->set_origin(origin);
+      scope_->MarkUnused(name_token_->value());
+    }
+  }
+  if (type_ == LIST)
+    return &list_->list_value()[index_];
+  return nullptr;
+}
+
+Value* ValueDestination::SetValue(Value value, const ParseNode* set_node) {
+  if (type_ == SCOPE) {
+    return scope_->SetValue(name_token_->value(), std::move(value), set_node);
+  } else if (type_ == LIST) {
+    Value* dest = &list_->list_value()[index_];
+    *dest = std::move(value);
+    return dest;
+  }
+  return nullptr;
+}
+
+void ValueDestination::MakeUndefinedIdentifierForModifyError(Err* err) {
+  // When Init() succeeds, the base of any accessor has already been resolved
+  // and that list indices are in-range. This means any undefined identifiers
+  // are for scope accesses.
+  DCHECK(type_ == SCOPE);
+  *err = Err(*name_token_, "Undefined identifier.");
+}
+
+// Computes an error message for overwriting a nonempty list/scope with another.
+Err MakeOverwriteError(const BinaryOpNode* op_node, const Value& old_value) {
+  std::string type_name;
+  std::string empty_def;
+
+  if (old_value.type() == Value::LIST) {
+    type_name = "list";
+    empty_def = "[]";
+  } else if (old_value.type() == Value::SCOPE) {
+    type_name = "scope";
+    empty_def = "{}";
+  } else {
+    NOTREACHED();
+  }
+
+  Err result(op_node->left()->GetRange(),
+             "Replacing nonempty " + type_name + ".",
+             "This overwrites a previously-defined nonempty " + type_name +
+                 " with another nonempty " + type_name + ".");
+  result.AppendSubErr(Err(
+      old_value, "for previous definition",
+      "Did you mean to append/modify instead? If you really want to overwrite, "
+      "do:\n"
+      "  foo = " +
+          empty_def + "\nbefore reassigning."));
+  return result;
+}
+
+// -----------------------------------------------------------------------------
+
+Err MakeIncompatibleTypeError(const BinaryOpNode* op_node,
+                              const Value& left,
+                              const Value& right) {
+  std::string msg = std::string("You can't do <") +
+                    Value::DescribeType(left.type()) + "> " +
+                    std::string(op_node->op().value()) + " <" +
+                    Value::DescribeType(right.type()) + ">.";
+  if (left.type() == Value::LIST) {
+    // Append extra hint for list stuff.
+    msg +=
+        "\n\nHint: If you're attempting to add or remove a single item from "
+        " a list, use \"foo + [ bar ]\".";
+  }
+  return Err(op_node, "Incompatible types for binary operator.", msg);
+}
+
+Value GetValueOrFillError(const BinaryOpNode* op_node,
+                          const ParseNode* node,
+                          const char* name,
+                          Scope* scope,
+                          Err* err) {
+  Value value = node->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  if (value.type() == Value::NONE) {
+    *err = Err(op_node->op(), "Operator requires a value.",
+               "This thing on the " + std::string(name) +
+                   " does not evaluate to a value.");
+    err->AppendRange(node->GetRange());
+    return Value();
+  }
+  return value;
+}
+
+void RemoveMatchesFromList(const BinaryOpNode* op_node,
+                           Value* list,
+                           const Value& to_remove,
+                           Err* err) {
+  std::vector<Value>& v = list->list_value();
+  switch (to_remove.type()) {
+    case Value::BOOLEAN:
+    case Value::INTEGER:  // Filter out the individual int/string.
+    case Value::STRING:
+    case Value::SCOPE: {
+      bool found_match = false;
+      for (size_t i = 0; i < v.size(); /* nothing */) {
+        if (v[i] == to_remove) {
+          found_match = true;
+          v.erase(v.begin() + i);
+        } else {
+          i++;
+        }
+      }
+      if (!found_match) {
+        *err = Err(to_remove.origin()->GetRange(), "Item not found",
+                   "You were trying to remove " + to_remove.ToString(true) +
+                       "\nfrom the list but it wasn't there.");
+      }
+      break;
+    }
+
+    case Value::LIST:  // Filter out each individual thing.
+      for (const auto& elem : to_remove.list_value()) {
+        // TODO(brettw) if the nested item is a list, we may want to search
+        // for the literal list rather than remote the items in it.
+        RemoveMatchesFromList(op_node, list, elem, err);
+        if (err->has_error())
+          return;
+      }
+      break;
+
+    case Value::NONE:
+      break;
+  }
+}
+
+// Assignment -----------------------------------------------------------------
+
+// We return a null value from this rather than the result of doing the append.
+// See ValuePlusEquals for rationale.
+Value ExecuteEquals(Scope* exec_scope,
+                    const BinaryOpNode* op_node,
+                    ValueDestination* dest,
+                    Value right,
+                    Err* err) {
+  const Value* old_value = dest->GetExistingValue();
+  if (old_value) {
+    // Check for overwriting nonempty scopes or lists with other nonempty
+    // scopes or lists. This prevents mistakes that clobber a value rather than
+    // appending to it. For cases where a user meant to clear a value, allow
+    // overwriting a nonempty list/scope with an empty one, which can then be
+    // modified.
+    if (old_value->type() == Value::LIST && right.type() == Value::LIST &&
+        !old_value->list_value().empty() && !right.list_value().empty()) {
+      *err = MakeOverwriteError(op_node, *old_value);
+      return Value();
+    } else if (old_value->type() == Value::SCOPE &&
+               right.type() == Value::SCOPE &&
+               old_value->scope_value()->HasValues(Scope::SEARCH_CURRENT) &&
+               right.scope_value()->HasValues(Scope::SEARCH_CURRENT)) {
+      *err = MakeOverwriteError(op_node, *old_value);
+      return Value();
+    }
+  }
+
+  dest->SetValue(std::move(right), op_node->right());
+  return Value();
+}
+
+// Plus/minus ------------------------------------------------------------------
+
+// allow_left_type_conversion indicates if we're allowed to change the type of
+// the left value. This is set to true when doing +, and false when doing +=.
+Value ExecutePlus(const BinaryOpNode* op_node,
+                  Value left,
+                  Value right,
+                  bool allow_left_type_conversion,
+                  Err* err) {
+  // Left-hand-side integer.
+  if (left.type() == Value::INTEGER) {
+    if (right.type() == Value::INTEGER) {
+      // Int + int -> addition.
+      return Value(op_node, left.int_value() + right.int_value());
+    } else if (right.type() == Value::STRING && allow_left_type_conversion) {
+      // Int + string -> string concat.
+      return Value(op_node, base::Int64ToString(left.int_value()) +
+                                right.string_value());
+    }
+    *err = MakeIncompatibleTypeError(op_node, left, right);
+    return Value();
+  }
+
+  // Left-hand-side string.
+  if (left.type() == Value::STRING) {
+    if (right.type() == Value::INTEGER) {
+      // String + int -> string concat.
+      return Value(op_node, left.string_value() +
+                                base::Int64ToString(right.int_value()));
+    } else if (right.type() == Value::STRING) {
+      // String + string -> string concat. Since the left is passed by copy
+      // we can avoid realloc if there is enough buffer by appending to left
+      // and assigning.
+      left.string_value().append(right.string_value());
+      return left;  // FIXME(brettw) des this copy?
+    }
+    *err = MakeIncompatibleTypeError(op_node, left, right);
+    return Value();
+  }
+
+  // Left-hand-side list. The only valid thing is to add another list.
+  if (left.type() == Value::LIST && right.type() == Value::LIST) {
+    // Since left was passed by copy, avoid realloc by destructively appending
+    // to it and using that as the result.
+    for (Value& value : right.list_value())
+      left.list_value().push_back(std::move(value));
+    return left;  // FIXME(brettw) does this copy?
+  }
+
+  *err = MakeIncompatibleTypeError(op_node, left, right);
+  return Value();
+}
+
+// Left is passed by value because it will be modified in-place and returned
+// for the list case.
+Value ExecuteMinus(const BinaryOpNode* op_node,
+                   Value left,
+                   const Value& right,
+                   Err* err) {
+  // Left-hand-side int. The only thing to do is subtract another int.
+  if (left.type() == Value::INTEGER && right.type() == Value::INTEGER) {
+    // Int - int -> subtraction.
+    return Value(op_node, left.int_value() - right.int_value());
+  }
+
+  // Left-hand-side list. The only thing to do is subtract another list.
+  if (left.type() == Value::LIST && right.type() == Value::LIST) {
+    // In-place modify left and return it.
+    RemoveMatchesFromList(op_node, &left, right, err);
+    return left;
+  }
+
+  *err = MakeIncompatibleTypeError(op_node, left, right);
+  return Value();
+}
+
+// In-place plus/minus ---------------------------------------------------------
+
+void ExecutePlusEquals(Scope* exec_scope,
+                       const BinaryOpNode* op_node,
+                       ValueDestination* dest,
+                       Value right,
+                       Err* err) {
+  // There are several cases. Some things we can convert "foo += bar" to
+  // "foo = foo + bar". Some cases we can't (the 'sources' variable won't
+  // get the right filtering on the list). Some cases we don't want to (lists
+  // and strings will get unnecessary copying so we can to optimize these).
+  //
+  //  - Value is already mutable in the current scope:
+  //     1. List/string append: use it.
+  //     2. Other types: fall back to "foo = foo + bar"
+  //
+  //  - Value is not mutable in the current scope:
+  //     3. List/string append: copy into current scope and append to that.
+  //     4. Other types: fall back to "foo = foo + bar"
+  //
+  // The common case is to use += for list and string appends in the local
+  // scope, so this is written to avoid multiple variable lookups in that case.
+  Value* mutable_dest = dest->GetExistingMutableValueIfExists(op_node);
+  if (!mutable_dest) {
+    const Value* existing_value = dest->GetExistingValue();
+    if (!existing_value) {
+      // Undefined left-hand-size for +=.
+      dest->MakeUndefinedIdentifierForModifyError(err);
+      return;
+    }
+
+    if (existing_value->type() != Value::STRING &&
+        existing_value->type() != Value::LIST) {
+      // Case #4 above.
+      dest->SetValue(
+          ExecutePlus(op_node, *existing_value, std::move(right), false, err),
+          op_node);
+      return;
+    }
+
+    // Case #3 above, copy to current scope and fall-through to appending.
+    mutable_dest = dest->SetValue(*existing_value, op_node);
+  } else if (mutable_dest->type() != Value::STRING &&
+             mutable_dest->type() != Value::LIST) {
+    // Case #2 above.
+    dest->SetValue(
+        ExecutePlus(op_node, *mutable_dest, std::move(right), false, err),
+        op_node);
+    return;
+  }  // "else" is case #1 above.
+
+  if (mutable_dest->type() == Value::STRING) {
+    if (right.type() == Value::INTEGER) {
+      // String + int -> string concat.
+      mutable_dest->string_value().append(
+          base::Int64ToString(right.int_value()));
+    } else if (right.type() == Value::STRING) {
+      // String + string -> string concat.
+      mutable_dest->string_value().append(right.string_value());
+    } else {
+      *err = MakeIncompatibleTypeError(op_node, *mutable_dest, right);
+    }
+  } else if (mutable_dest->type() == Value::LIST) {
+    // List concat.
+    if (right.type() == Value::LIST) {
+      // Normal list concat. This is a destructive move.
+      for (Value& value : right.list_value())
+        mutable_dest->list_value().push_back(std::move(value));
+    } else {
+      *err = Err(op_node->op(), "Incompatible types to add.",
+                 "To append a single item to a list do \"foo += [ bar ]\".");
+    }
+  }
+}
+
+void ExecuteMinusEquals(const BinaryOpNode* op_node,
+                        ValueDestination* dest,
+                        const Value& right,
+                        Err* err) {
+  // Like the += case, we can convert "foo -= bar" to "foo = foo - bar". Since
+  // there is no sources filtering, this is always semantically valid. The
+  // only case we don't do it is for lists in the current scope which is the
+  // most common case, and also the one that can be optimized the most by
+  // doing it in-place.
+  Value* mutable_dest = dest->GetExistingMutableValueIfExists(op_node);
+  if (!mutable_dest ||
+      (mutable_dest->type() != Value::LIST || right.type() != Value::LIST)) {
+    const Value* existing_value = dest->GetExistingValue();
+    if (!existing_value) {
+      // Undefined left-hand-size for -=.
+      dest->MakeUndefinedIdentifierForModifyError(err);
+      return;
+    }
+    dest->SetValue(ExecuteMinus(op_node, *existing_value, right, err), op_node);
+    return;
+  }
+
+  // In-place removal of items from "right".
+  RemoveMatchesFromList(op_node, mutable_dest, right, err);
+}
+
+// Comparison -----------------------------------------------------------------
+
+Value ExecuteEqualsEquals(Scope* scope,
+                          const BinaryOpNode* op_node,
+                          const Value& left,
+                          const Value& right,
+                          Err* err) {
+  if (left == right)
+    return Value(op_node, true);
+  return Value(op_node, false);
+}
+
+Value ExecuteNotEquals(Scope* scope,
+                       const BinaryOpNode* op_node,
+                       const Value& left,
+                       const Value& right,
+                       Err* err) {
+  // Evaluate in terms of ==.
+  Value result = ExecuteEqualsEquals(scope, op_node, left, right, err);
+  result.boolean_value() = !result.boolean_value();
+  return result;
+}
+
+Value FillNeedsTwoIntegersError(const BinaryOpNode* op_node,
+                                const Value& left,
+                                const Value& right,
+                                Err* err) {
+  *err = Err(op_node, "Comparison requires two integers.",
+             "This operator can only compare two integers.");
+  err->AppendRange(left.origin()->GetRange());
+  err->AppendRange(right.origin()->GetRange());
+  return Value();
+}
+
+Value ExecuteLessEquals(Scope* scope,
+                        const BinaryOpNode* op_node,
+                        const Value& left,
+                        const Value& right,
+                        Err* err) {
+  if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
+    return FillNeedsTwoIntegersError(op_node, left, right, err);
+  return Value(op_node, left.int_value() <= right.int_value());
+}
+
+Value ExecuteGreaterEquals(Scope* scope,
+                           const BinaryOpNode* op_node,
+                           const Value& left,
+                           const Value& right,
+                           Err* err) {
+  if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
+    return FillNeedsTwoIntegersError(op_node, left, right, err);
+  return Value(op_node, left.int_value() >= right.int_value());
+}
+
+Value ExecuteGreater(Scope* scope,
+                     const BinaryOpNode* op_node,
+                     const Value& left,
+                     const Value& right,
+                     Err* err) {
+  if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
+    return FillNeedsTwoIntegersError(op_node, left, right, err);
+  return Value(op_node, left.int_value() > right.int_value());
+}
+
+Value ExecuteLess(Scope* scope,
+                  const BinaryOpNode* op_node,
+                  const Value& left,
+                  const Value& right,
+                  Err* err) {
+  if (left.type() != Value::INTEGER || right.type() != Value::INTEGER)
+    return FillNeedsTwoIntegersError(op_node, left, right, err);
+  return Value(op_node, left.int_value() < right.int_value());
+}
+
+// Binary ----------------------------------------------------------------------
+
+Value ExecuteOr(Scope* scope,
+                const BinaryOpNode* op_node,
+                const ParseNode* left_node,
+                const ParseNode* right_node,
+                Err* err) {
+  Value left = GetValueOrFillError(op_node, left_node, "left", scope, err);
+  if (err->has_error())
+    return Value();
+  if (left.type() != Value::BOOLEAN) {
+    *err = Err(op_node->left(), "Left side of || operator is not a boolean.",
+               "Type is \"" + std::string(Value::DescribeType(left.type())) +
+                   "\" instead.");
+    return Value();
+  }
+  if (left.boolean_value())
+    return Value(op_node, left.boolean_value());
+
+  Value right = GetValueOrFillError(op_node, right_node, "right", scope, err);
+  if (err->has_error())
+    return Value();
+  if (right.type() != Value::BOOLEAN) {
+    *err = Err(op_node->right(), "Right side of || operator is not a boolean.",
+               "Type is \"" + std::string(Value::DescribeType(right.type())) +
+                   "\" instead.");
+    return Value();
+  }
+
+  return Value(op_node, left.boolean_value() || right.boolean_value());
+}
+
+Value ExecuteAnd(Scope* scope,
+                 const BinaryOpNode* op_node,
+                 const ParseNode* left_node,
+                 const ParseNode* right_node,
+                 Err* err) {
+  Value left = GetValueOrFillError(op_node, left_node, "left", scope, err);
+  if (err->has_error())
+    return Value();
+  if (left.type() != Value::BOOLEAN) {
+    *err = Err(op_node->left(), "Left side of && operator is not a boolean.",
+               "Type is \"" + std::string(Value::DescribeType(left.type())) +
+                   "\" instead.");
+    return Value();
+  }
+  if (!left.boolean_value())
+    return Value(op_node, left.boolean_value());
+
+  Value right = GetValueOrFillError(op_node, right_node, "right", scope, err);
+  if (err->has_error())
+    return Value();
+  if (right.type() != Value::BOOLEAN) {
+    *err = Err(op_node->right(), "Right side of && operator is not a boolean.",
+               "Type is \"" + std::string(Value::DescribeType(right.type())) +
+                   "\" instead.");
+    return Value();
+  }
+  return Value(op_node, left.boolean_value() && right.boolean_value());
+}
+
+}  // namespace
+
+// ----------------------------------------------------------------------------
+
+Value ExecuteUnaryOperator(Scope* scope,
+                           const UnaryOpNode* op_node,
+                           const Value& expr,
+                           Err* err) {
+  DCHECK(op_node->op().type() == Token::BANG);
+
+  if (expr.type() != Value::BOOLEAN) {
+    *err = Err(op_node, "Operand of ! operator is not a boolean.",
+               "Type is \"" + std::string(Value::DescribeType(expr.type())) +
+                   "\" instead.");
+    return Value();
+  }
+  // TODO(scottmg): Why no unary minus?
+  return Value(op_node, !expr.boolean_value());
+}
+
+Value ExecuteBinaryOperator(Scope* scope,
+                            const BinaryOpNode* op_node,
+                            const ParseNode* left,
+                            const ParseNode* right,
+                            Err* err) {
+  const Token& op = op_node->op();
+
+  // First handle the ones that take an lvalue.
+  if (op.type() == Token::EQUAL || op.type() == Token::PLUS_EQUALS ||
+      op.type() == Token::MINUS_EQUALS) {
+    // Compute the left side.
+    ValueDestination dest;
+    if (!dest.Init(scope, left, op_node, err))
+      return Value();
+
+    // Compute the right side.
+    Value right_value = right->Execute(scope, err);
+    if (err->has_error())
+      return Value();
+    if (right_value.type() == Value::NONE) {
+      *err = Err(op, "Operator requires a rvalue.",
+                 "This thing on the right does not evaluate to a value.");
+      err->AppendRange(right->GetRange());
+      return Value();
+    }
+
+    // "foo += bar" (same for "-=") is converted to "foo = foo + bar" here, but
+    // we pass the original value of "foo" by pointer to avoid a copy.
+    if (op.type() == Token::EQUAL) {
+      ExecuteEquals(scope, op_node, &dest, std::move(right_value), err);
+    } else if (op.type() == Token::PLUS_EQUALS) {
+      ExecutePlusEquals(scope, op_node, &dest, std::move(right_value), err);
+    } else if (op.type() == Token::MINUS_EQUALS) {
+      ExecuteMinusEquals(op_node, &dest, right_value, err);
+    } else {
+      NOTREACHED();
+    }
+    return Value();
+  }
+
+  // ||, &&. Passed the node instead of the value so that they can avoid
+  // evaluating the RHS on early-out.
+  if (op.type() == Token::BOOLEAN_OR)
+    return ExecuteOr(scope, op_node, left, right, err);
+  if (op.type() == Token::BOOLEAN_AND)
+    return ExecuteAnd(scope, op_node, left, right, err);
+
+  // Everything else works on the evaluated left and right values.
+  Value left_value = GetValueOrFillError(op_node, left, "left", scope, err);
+  if (err->has_error())
+    return Value();
+  Value right_value = GetValueOrFillError(op_node, right, "right", scope, err);
+  if (err->has_error())
+    return Value();
+
+  // +, -.
+  if (op.type() == Token::MINUS)
+    return ExecuteMinus(op_node, std::move(left_value), right_value, err);
+  if (op.type() == Token::PLUS) {
+    return ExecutePlus(op_node, std::move(left_value), std::move(right_value),
+                       true, err);
+  }
+
+  // Comparisons.
+  if (op.type() == Token::EQUAL_EQUAL)
+    return ExecuteEqualsEquals(scope, op_node, left_value, right_value, err);
+  if (op.type() == Token::NOT_EQUAL)
+    return ExecuteNotEquals(scope, op_node, left_value, right_value, err);
+  if (op.type() == Token::GREATER_EQUAL)
+    return ExecuteGreaterEquals(scope, op_node, left_value, right_value, err);
+  if (op.type() == Token::LESS_EQUAL)
+    return ExecuteLessEquals(scope, op_node, left_value, right_value, err);
+  if (op.type() == Token::GREATER_THAN)
+    return ExecuteGreater(scope, op_node, left_value, right_value, err);
+  if (op.type() == Token::LESS_THAN)
+    return ExecuteLess(scope, op_node, left_value, right_value, err);
+
+  return Value();
+}
diff --git a/src/gn/operators.h b/src/gn/operators.h
new file mode 100644 (file)
index 0000000..82ff68e
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_OPERATORS_H_
+#define TOOLS_GN_OPERATORS_H_
+
+class BinaryOpNode;
+class Err;
+class ParseNode;
+class Scope;
+class UnaryOpNode;
+class Value;
+
+Value ExecuteUnaryOperator(Scope* scope,
+                           const UnaryOpNode* op_node,
+                           const Value& value,
+                           Err* err);
+Value ExecuteBinaryOperator(Scope* scope,
+                            const BinaryOpNode* op_node,
+                            const ParseNode* left,
+                            const ParseNode* right,
+                            Err* err);
+
+#endif  // TOOLS_GN_OPERATORS_H_
diff --git a/src/gn/operators_unittest.cc b/src/gn/operators_unittest.cc
new file mode 100644 (file)
index 0000000..415596c
--- /dev/null
@@ -0,0 +1,413 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/operators.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "gn/parse_tree.h"
+#include "gn/pattern.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool IsValueIntegerEqualing(const Value& v, int64_t i) {
+  if (v.type() != Value::INTEGER)
+    return false;
+  return v.int_value() == i;
+}
+
+bool IsValueStringEqualing(const Value& v, const char* s) {
+  if (v.type() != Value::STRING)
+    return false;
+  return v.string_value() == s;
+}
+
+// This parse node is for passing to tests. It returns a canned value for
+// Execute().
+class TestParseNode : public ParseNode {
+ public:
+  TestParseNode(const Value& v) : value_(v) {}
+
+  Value Execute(Scope* scope, Err* err) const override { return value_; }
+  LocationRange GetRange() const override { return LocationRange(); }
+  Err MakeErrorDescribing(const std::string& msg,
+                          const std::string& help) const override {
+    return Err(this, msg);
+  }
+  base::Value GetJSONNode() const override {
+    return base::Value();
+  }
+
+ private:
+  Value value_;
+};
+
+// Sets up a BinaryOpNode for testing.
+class TestBinaryOpNode : public BinaryOpNode {
+ public:
+  // Input token value string must outlive class.
+  TestBinaryOpNode(Token::Type op_token_type, const char* op_token_value)
+      : BinaryOpNode(),
+        op_token_ownership_(Location(), op_token_type, op_token_value) {
+    set_op(op_token_ownership_);
+  }
+
+  void SetLeftToValue(const Value& value) {
+    set_left(std::make_unique<TestParseNode>(value));
+  }
+
+  // Sets the left-hand side of the operator to an identifier node, this is
+  // used for testing assignments. Input string must outlive class.
+  void SetLeftToIdentifier(const char* identifier) {
+    left_identifier_token_ownership_ =
+        Token(Location(), Token::IDENTIFIER, identifier);
+    set_left(
+        std::make_unique<IdentifierNode>(left_identifier_token_ownership_));
+  }
+
+  void SetRightToValue(const Value& value) {
+    set_right(std::make_unique<TestParseNode>(value));
+  }
+  void SetRightToListOfValue(const Value& value) {
+    Value list(nullptr, Value::LIST);
+    list.list_value().push_back(value);
+    set_right(std::make_unique<TestParseNode>(list));
+  }
+  void SetRightToListOfValue(const Value& value1, const Value& value2) {
+    Value list(nullptr, Value::LIST);
+    list.list_value().push_back(value1);
+    list.list_value().push_back(value2);
+    set_right(std::make_unique<TestParseNode>(list));
+  }
+
+ private:
+  // The base class takes the Token by reference, this manages the lifetime.
+  Token op_token_ownership_;
+
+  // When setting the left to an identifier, this manages the lifetime of
+  // the identifier token.
+  Token left_identifier_token_ownership_;
+};
+
+}  // namespace
+
+TEST(Operators, SourcesAppend) {
+  Err err;
+  TestWithScope setup;
+
+  // Set up "sources" with an empty list.
+  const char sources[] = "sources";
+  setup.scope()->SetValue(sources, Value(nullptr, Value::LIST), nullptr);
+
+  // Set up the operator.
+  TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
+  node.SetLeftToIdentifier(sources);
+
+  // Append an integer.
+  node.SetRightToListOfValue(Value(nullptr, static_cast<int64_t>(5)));
+  node.Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Append a string that doesn't match the pattern, it should get appended.
+  const char string1[] = "good";
+  node.SetRightToListOfValue(Value(nullptr, string1));
+  node.Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Append a list with the two strings from above.
+  node.SetRightToListOfValue(Value(nullptr, string1));
+  node.Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // The sources variable in the scope should now have: [ 5, "good", "good" ]
+  const Value* value = setup.scope()->GetValue(sources);
+  ASSERT_TRUE(value);
+  ASSERT_EQ(Value::LIST, value->type());
+  ASSERT_EQ(3u, value->list_value().size());
+  EXPECT_TRUE(IsValueIntegerEqualing(value->list_value()[0], 5));
+  EXPECT_TRUE(IsValueStringEqualing(value->list_value()[1], "good"));
+  EXPECT_TRUE(IsValueStringEqualing(value->list_value()[2], "good"));
+}
+
+// Note that the SourcesAppend test above tests the basic list + list features,
+// this test handles the other cases.
+TEST(Operators, ListAppend) {
+  Err err;
+  TestWithScope setup;
+
+  // Set up "foo" with an empty list.
+  const char foo[] = "foo";
+  setup.scope()->SetValue(foo, Value(nullptr, Value::LIST), nullptr);
+
+  // Set up the operator to append to "foo".
+  TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
+  node.SetLeftToIdentifier(foo);
+
+  // Append a list with a list, the result should be a nested list.
+  Value inner_list(nullptr, Value::LIST);
+  inner_list.list_value().push_back(Value(nullptr, static_cast<int64_t>(12)));
+  node.SetRightToListOfValue(inner_list);
+
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Return from the operator should always be "none", it should update the
+  // value only.
+  EXPECT_EQ(Value::NONE, ret.type());
+
+  // The value should be updated with "[ [ 12 ] ]"
+  Value result = *setup.scope()->GetValue(foo);
+  ASSERT_EQ(Value::LIST, result.type());
+  ASSERT_EQ(1u, result.list_value().size());
+  ASSERT_EQ(Value::LIST, result.list_value()[0].type());
+  ASSERT_EQ(1u, result.list_value()[0].list_value().size());
+  ASSERT_EQ(Value::INTEGER, result.list_value()[0].list_value()[0].type());
+  ASSERT_EQ(12, result.list_value()[0].list_value()[0].int_value());
+
+  // Try to append an integer and a string directly (e.g. foo += "hi").
+  // This should fail.
+  const char str_str[] = "\"hi\"";
+  Token str(Location(), Token::STRING, str_str);
+  node.set_right(std::make_unique<LiteralNode>(str));
+  ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+
+  node.SetRightToValue(Value(nullptr, static_cast<int64_t>(12)));
+  ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(Operators, ListRemove) {
+  Err err;
+  TestWithScope setup;
+
+  const char foo_str[] = "foo";
+  const char bar_str[] = "bar";
+  Value test_list(nullptr, Value::LIST);
+  test_list.list_value().push_back(Value(nullptr, foo_str));
+  test_list.list_value().push_back(Value(nullptr, bar_str));
+  test_list.list_value().push_back(Value(nullptr, foo_str));
+
+  // Set up "var" with an the test list.
+  const char var[] = "var";
+  setup.scope()->SetValue(var, test_list, nullptr);
+
+  TestBinaryOpNode node(Token::MINUS_EQUALS, "-=");
+  node.SetLeftToIdentifier(var);
+
+  // Subtract a list consisting of "foo".
+  node.SetRightToListOfValue(Value(nullptr, foo_str));
+  Value result = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                       node.right(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // -= returns an empty value to reduce the possibility of writing confusing
+  // cases like foo = bar += 1.
+  EXPECT_EQ(Value::NONE, result.type());
+
+  // The "var" variable should have been updated. Both instances of "foo" are
+  // deleted.
+  const Value* new_value = setup.scope()->GetValue(var);
+  ASSERT_TRUE(new_value);
+  ASSERT_EQ(Value::LIST, new_value->type());
+  ASSERT_EQ(1u, new_value->list_value().size());
+  ASSERT_EQ(Value::STRING, new_value->list_value()[0].type());
+  EXPECT_EQ("bar", new_value->list_value()[0].string_value());
+}
+
+TEST(Operators, ListSubtractWithScope) {
+  Err err;
+  TestWithScope setup;
+
+  Scope* scope_a = new Scope(setup.settings());
+  Value scopeval_a(nullptr, std::unique_ptr<Scope>(scope_a));
+  scope_a->SetValue("a", Value(nullptr, "foo"), nullptr);
+
+  Scope* scope_b = new Scope(setup.settings());
+  Value scopeval_b(nullptr, std::unique_ptr<Scope>(scope_b));
+  scope_b->SetValue("b", Value(nullptr, "bar"), nullptr);
+
+  Value lval(nullptr, Value::LIST);
+  lval.list_value().push_back(scopeval_a);
+  lval.list_value().push_back(scopeval_b);
+
+  Scope* scope_a_other = new Scope(setup.settings());
+  Value scopeval_a_other(nullptr, std::unique_ptr<Scope>(scope_a_other));
+  scope_a_other->SetValue("a", Value(nullptr, "foo"), nullptr);
+
+  Value rval(nullptr, Value::LIST);
+  rval.list_value().push_back(scopeval_a_other);
+
+  TestBinaryOpNode node(Token::MINUS, "-");
+  node.SetLeftToValue(lval);
+  node.SetRightToValue(rval);
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(Value::LIST, ret.type());
+
+  std::vector<Value> expected;
+  Scope* scope_expected = new Scope(setup.settings());
+  Value scopeval_expected(nullptr, std::unique_ptr<Scope>(scope_expected));
+  scope_expected->SetValue("b", Value(nullptr, "bar"), nullptr);
+  expected.push_back(scopeval_expected);
+  EXPECT_EQ(expected, ret.list_value());
+}
+
+TEST(Operators, IntegerAdd) {
+  Err err;
+  TestWithScope setup;
+
+  TestBinaryOpNode node(Token::PLUS, "+");
+  node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
+  node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(Value::INTEGER, ret.type());
+  EXPECT_EQ(579, ret.int_value());
+}
+
+TEST(Operators, IntegerSubtract) {
+  Err err;
+  TestWithScope setup;
+
+  TestBinaryOpNode node(Token::MINUS, "-");
+  node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
+  node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(Value::INTEGER, ret.type());
+  EXPECT_EQ(-333, ret.int_value());
+}
+
+TEST(Operators, ShortCircuitAnd) {
+  Err err;
+  TestWithScope setup;
+
+  // Set a && operator with the left to false.
+  TestBinaryOpNode node(Token::BOOLEAN_AND, "&&");
+  node.SetLeftToValue(Value(nullptr, false));
+
+  // Set right as foo, but don't define a value for it.
+  const char foo[] = "foo";
+  Token identifier_token(Location(), Token::IDENTIFIER, foo);
+  node.set_right(std::make_unique<IdentifierNode>(identifier_token));
+
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  EXPECT_FALSE(err.has_error());
+}
+
+TEST(Operators, ShortCircuitOr) {
+  Err err;
+  TestWithScope setup;
+
+  // Set a || operator with the left to true.
+  TestBinaryOpNode node(Token::BOOLEAN_OR, "||");
+  node.SetLeftToValue(Value(nullptr, true));
+
+  // Set right as foo, but don't define a value for it.
+  const char foo[] = "foo";
+  Token identifier_token(Location(), Token::IDENTIFIER, foo);
+  node.set_right(std::make_unique<IdentifierNode>(identifier_token));
+
+  Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
+                                    node.right(), &err);
+  EXPECT_FALSE(err.has_error());
+}
+
+// Overwriting nonempty lists and scopes with other nonempty lists and scopes
+// should be disallowed.
+TEST(Operators, NonemptyOverwriting) {
+  Err err;
+  TestWithScope setup;
+
+  // Set up "foo" with a nonempty list.
+  const char foo[] = "foo";
+  Value old_value(nullptr, Value::LIST);
+  old_value.list_value().push_back(Value(nullptr, "string"));
+  setup.scope()->SetValue(foo, old_value, nullptr);
+
+  TestBinaryOpNode node(Token::EQUAL, "=");
+  node.SetLeftToIdentifier(foo);
+
+  // Assigning a nonempty list should fail.
+  node.SetRightToListOfValue(Value(nullptr, "string"));
+  node.Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Replacing nonempty list.", err.message());
+  err = Err();
+
+  // Assigning an empty list should succeed.
+  node.SetRightToValue(Value(nullptr, Value::LIST));
+  node.Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error());
+  const Value* new_value = setup.scope()->GetValue(foo);
+  ASSERT_TRUE(new_value);
+  ASSERT_EQ(Value::LIST, new_value->type());
+  ASSERT_TRUE(new_value->list_value().empty());
+
+  // Set up "foo" with a nonempty scope.
+  const char bar[] = "bar";
+  old_value = Value(nullptr, std::make_unique<Scope>(setup.settings()));
+  old_value.scope_value()->SetValue(bar, Value(nullptr, "bar"), nullptr);
+  setup.scope()->SetValue(foo, old_value, nullptr);
+
+  // Assigning a nonempty scope should fail (re-use old_value copy).
+  node.SetRightToValue(old_value);
+  node.Execute(setup.scope(), &err);
+  ASSERT_TRUE(err.has_error());
+  EXPECT_EQ("Replacing nonempty scope.", err.message());
+  err = Err();
+
+  // Assigning an empty list should succeed.
+  node.SetRightToValue(
+      Value(nullptr, std::make_unique<Scope>(setup.settings())));
+  node.Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error());
+  new_value = setup.scope()->GetValue(foo);
+  ASSERT_TRUE(new_value);
+  ASSERT_EQ(Value::SCOPE, new_value->type());
+  ASSERT_FALSE(new_value->scope_value()->HasValues(Scope::SEARCH_CURRENT));
+}
+
+// Tests this case:
+//  foo = 1
+//  target(...) {
+//    foo += 1
+//
+// This should mark the outer "foo" as used, and the inner "foo" as unused.
+TEST(Operators, PlusEqualsUsed) {
+  Err err;
+  TestWithScope setup;
+
+  // Outer "foo" definition, it should be unused.
+  const char foo[] = "foo";
+  Value old_value(nullptr, static_cast<int64_t>(1));
+  setup.scope()->SetValue(foo, old_value, nullptr);
+  EXPECT_TRUE(setup.scope()->IsSetButUnused(foo));
+
+  // Nested scope.
+  Scope nested(setup.scope());
+
+  // Run "foo += 1".
+  TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
+  node.SetLeftToIdentifier(foo);
+  node.SetRightToValue(Value(nullptr, static_cast<int64_t>(1)));
+  node.Execute(&nested, &err);
+  ASSERT_FALSE(err.has_error());
+
+  // Outer foo should be used, inner foo should not be.
+  EXPECT_FALSE(setup.scope()->IsSetButUnused(foo));
+  EXPECT_TRUE(nested.IsSetButUnused(foo));
+}
diff --git a/src/gn/ordered_set.h b/src/gn/ordered_set.h
new file mode 100644 (file)
index 0000000..fda4e12
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_ORDERED_SET_H_
+#define TOOLS_GN_ORDERED_SET_H_
+
+#include <stddef.h>
+
+#include <set>
+
+// An ordered set of items. Only appending is supported. Iteration is designed
+// to be by index.
+template <typename T>
+class OrderedSet {
+ private:
+  using set_type = std::set<T>;
+  using set_iterator = typename set_type::const_iterator;
+  using vector_type = std::vector<set_iterator>;
+
+ public:
+  static const size_t npos = static_cast<size_t>(-1);
+
+  OrderedSet() {}
+  ~OrderedSet() {}
+
+  const T& operator[](size_t index) const { return *ordering_[index]; }
+  size_t size() const { return ordering_.size(); }
+  bool empty() const { return ordering_.empty(); }
+
+  bool has_item(const T& t) const { return set_.find(t) != set_.end(); }
+
+  // Returns true if the item was inserted. False if it was already in the
+  // set.
+  bool push_back(const T& t) {
+    std::pair<set_iterator, bool> result = set_.insert(t);
+    if (result.second)
+      ordering_.push_back(result.first);
+    return true;
+  }
+
+  // Appends a range of items, skipping ones that already exist.
+  template <class InputIterator>
+  void append(const InputIterator& insert_begin,
+              const InputIterator& insert_end) {
+    for (InputIterator i = insert_begin; i != insert_end; ++i) {
+      const T& t = *i;
+      push_back(t);
+    }
+  }
+
+  // Appends all items from the given other set.
+  void append(const OrderedSet<T>& other) {
+    for (size_t i = 0; i < other.size(); i++)
+      push_back(other[i]);
+  }
+
+ private:
+  set_type set_;
+  vector_type ordering_;
+};
+
+#endif  // TOOLS_GN_ORDERED_SET_H_
diff --git a/src/gn/output_conversion.cc b/src/gn/output_conversion.cc
new file mode 100644 (file)
index 0000000..04017dd
--- /dev/null
@@ -0,0 +1,177 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/output_conversion.h"
+
+#include "gn/settings.h"
+#include "gn/value.h"
+
+namespace {
+
+void ToString(const Value& output, std::ostream& out) {
+  out << output.ToString(false);
+}
+
+void ToStringQuoted(const Value& output, std::ostream& out) {
+  out << "\"" << output.ToString(false) << "\"";
+}
+
+void Indent(int indent, std::ostream& out) {
+  for (int i = 0; i < indent; ++i)
+    out << "  ";
+}
+
+// Forward declare so it can be used recursively.
+void RenderScopeToJSON(const Value& output, std::ostream& out, int indent);
+
+void RenderListToJSON(const Value& output, std::ostream& out, int indent) {
+  assert(indent > 0);
+  bool first = true;
+  out << "[\n";
+  for (const auto& value : output.list_value()) {
+    if (!first)
+      out << ",\n";
+    Indent(indent, out);
+    if (value.type() == Value::SCOPE)
+      RenderScopeToJSON(value, out, indent + 1);
+    else if (value.type() == Value::LIST)
+      RenderListToJSON(value, out, indent + 1);
+    else
+      out << value.ToString(true);
+    first = false;
+  }
+  out << "\n";
+  Indent(indent - 1, out);
+  out << "]";
+}
+
+void RenderScopeToJSON(const Value& output, std::ostream& out, int indent) {
+  assert(indent > 0);
+  Scope::KeyValueMap scope_values;
+  output.scope_value()->GetCurrentScopeValues(&scope_values);
+  bool first = true;
+  out << "{\n";
+  for (const auto& pair : scope_values) {
+    if (!first)
+      out << ",\n";
+    Indent(indent, out);
+    out << "\"" << pair.first << "\": ";
+    if (pair.second.type() == Value::SCOPE)
+      RenderScopeToJSON(pair.second, out, indent + 1);
+    else if (pair.second.type() == Value::LIST)
+      RenderListToJSON(pair.second, out, indent + 1);
+    else
+      out << pair.second.ToString(true);
+    first = false;
+  }
+  out << "\n";
+  Indent(indent - 1, out);
+  out << "}";
+}
+
+void OutputListLines(const Value& output, std::ostream& out) {
+  assert(output.type() == Value::LIST);
+  const std::vector<Value>& list = output.list_value();
+  for (const auto& cur : list)
+    out << cur.ToString(false) << "\n";
+}
+
+void OutputString(const Value& output, std::ostream& out) {
+  if (output.type() == Value::NONE)
+    return;
+  if (output.type() == Value::STRING) {
+    ToString(output, out);
+    return;
+  }
+  ToStringQuoted(output, out);
+}
+
+void OutputValue(const Value& output, std::ostream& out) {
+  if (output.type() == Value::NONE)
+    return;
+  if (output.type() == Value::STRING) {
+    ToStringQuoted(output, out);
+    return;
+  }
+  ToString(output, out);
+}
+
+// The direct Value::ToString call wraps the scope in '{}', which we don't want
+// here for the top-level scope being output.
+void OutputScope(const Value& output, std::ostream& out) {
+  Scope::KeyValueMap scope_values;
+  output.scope_value()->GetCurrentScopeValues(&scope_values);
+  for (const auto& pair : scope_values) {
+    out << "  " << pair.first << " = " << pair.second.ToString(true) << "\n";
+  }
+}
+
+void OutputDefault(const Value& output, std::ostream& out) {
+  if (output.type() == Value::LIST)
+    OutputListLines(output, out);
+  else
+    ToString(output, out);
+}
+
+void OutputJSON(const Value& output, std::ostream& out) {
+  if (output.type() == Value::SCOPE) {
+    RenderScopeToJSON(output, out, /*indent=*/1);
+    return;
+  }
+  if (output.type() == Value::LIST) {
+    RenderListToJSON(output, out, /*indent=*/1);
+    return;
+  }
+  ToStringQuoted(output, out);
+}
+
+void DoConvertValueToOutput(const Value& output,
+                            const std::string& output_conversion,
+                            const Value& original_output_conversion,
+                            std::ostream& out,
+                            Err* err) {
+  if (output_conversion == "") {
+    OutputDefault(output, out);
+  } else if (output_conversion == "list lines") {
+    if (output.type() != Value::LIST) {
+      *err = Err(original_output_conversion, "Not a valid list.");
+      return;
+    }
+    OutputListLines(output, out);
+  } else if (output_conversion == "string") {
+    OutputString(output, out);
+  } else if (output_conversion == "value") {
+    OutputValue(output, out);
+  } else if (output_conversion == "json") {
+    OutputJSON(output, out);
+  } else if (output_conversion == "scope") {
+    if (output.type() != Value::SCOPE) {
+      *err = Err(original_output_conversion, "Not a valid scope.");
+      return;
+    }
+    OutputScope(output, out);
+  } else {
+    // If we make it here, we didn't match any of the valid options.
+    *err = Err(original_output_conversion, "Not a valid output_conversion.",
+               "Run gn help output_conversion to see your options.");
+  }
+}
+
+}  // namespace
+
+void ConvertValueToOutput(const Settings* settings,
+                          const Value& output,
+                          const Value& output_conversion,
+                          std::ostream& out,
+                          Err* err) {
+  if (output_conversion.type() == Value::NONE) {
+    OutputDefault(output, out);
+    return;
+  }
+  if (!output_conversion.VerifyTypeIs(Value::STRING, err))
+    return;
+
+  DoConvertValueToOutput(output, output_conversion.string_value(),
+                         output_conversion, out, err);
+}
diff --git a/src/gn/output_conversion.h b/src/gn/output_conversion.h
new file mode 100644 (file)
index 0000000..09ca254
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_OUTPUT_CONVERSION_H_
+#define TOOLS_GN_OUTPUT_CONVERSION_H_
+
+#include <iosfwd>
+#include <string>
+
+class Err;
+class Settings;
+class Value;
+
+// Converts the given input Value to an output string (to be written to a file).
+// Conversions as specified in the output_conversion string will be performed.
+// The given ostream will be used for writing the resulting string.
+//
+// If the conversion string is invalid, the error will be set.
+void ConvertValueToOutput(const Settings* settings,
+                          const Value& output,
+                          const Value& output_conversion_value,
+                          std::ostream& out,
+                          Err* err);
+
+#endif  // TOOLS_GN_OUTPUT_CONVERSION_H_
diff --git a/src/gn/output_conversion_unittest.cc b/src/gn/output_conversion_unittest.cc
new file mode 100644 (file)
index 0000000..43fdad5
--- /dev/null
@@ -0,0 +1,351 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/output_conversion.h"
+
+#include <sstream>
+
+#include "gn/err.h"
+#include "gn/input_conversion.h"
+#include "gn/scope.h"
+#include "gn/template.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+namespace {
+
+// InputConversion needs a global scheduler object.
+class OutputConversionTest : public TestWithScheduler {
+ public:
+  OutputConversionTest() = default;
+
+  const Settings* settings() { return setup_.settings(); }
+  Scope* scope() { return setup_.scope(); }
+
+ private:
+  TestWithScope setup_;
+};
+
+}  // namespace
+
+TEST_F(OutputConversionTest, ListLines) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "list lines"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
+}
+
+TEST_F(OutputConversionTest, String) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "foo bar");
+}
+
+TEST_F(OutputConversionTest, StringInt) {
+  Err err;
+  Value output(nullptr, int64_t(6));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"6\"");
+}
+
+TEST_F(OutputConversionTest, StringBool) {
+  Err err;
+  Value output(nullptr, true);
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"true\"");
+}
+
+TEST_F(OutputConversionTest, StringList) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  output.list_value().push_back(Value(nullptr, int64_t(6)));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "string"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"[\"foo\", \"bar\", 6]\"");
+}
+
+TEST_F(OutputConversionTest, StringScope) {
+  Err err;
+
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value value(nullptr, "hello");
+  new_scope->SetValue("v", value, nullptr);
+  std::string_view private_var_name("_private");
+  new_scope->SetValue(private_var_name, value, nullptr);
+
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "string"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"{\n  _private = \"hello\"\n  v = \"hello\"\n}\"");
+}
+
+TEST_F(OutputConversionTest, ValueString) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "\"foo bar\"");
+}
+
+TEST_F(OutputConversionTest, ValueInt) {
+  Err err;
+  Value output(nullptr, int64_t(6));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "6");
+}
+
+TEST_F(OutputConversionTest, ValueBool) {
+  Err err;
+  Value output(nullptr, true);
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "true");
+}
+
+TEST_F(OutputConversionTest, ValueList) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  output.list_value().push_back(Value(nullptr, int64_t(6)));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, "value"), result,
+                       &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "[\"foo\", \"bar\", 6]");
+}
+
+TEST_F(OutputConversionTest, ValueScope) {
+  Err err;
+
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value value(nullptr, "hello");
+  new_scope->SetValue("v", value, nullptr);
+  std::string_view private_var_name("_private");
+  new_scope->SetValue(private_var_name, value, nullptr);
+
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "value"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "{\n  _private = \"hello\"\n  v = \"hello\"\n}");
+}
+
+TEST_F(OutputConversionTest, JSON) {
+  Err err;
+  auto new_scope = std::make_unique<Scope>(settings());
+  // Add some values to the scope.
+  Value a_value(nullptr, "foo");
+  new_scope->SetValue("a", a_value, nullptr);
+  Value b_value(nullptr, int64_t(6));
+  new_scope->SetValue("b", b_value, nullptr);
+
+  auto c_scope = std::make_unique<Scope>(settings());
+  Value e_value(nullptr, Value::LIST);
+  e_value.list_value().push_back(Value(nullptr, "bar"));
+
+  auto e_value_scope = std::make_unique<Scope>(settings());
+  Value f_value(nullptr, "baz");
+  e_value_scope->SetValue("f", f_value, nullptr);
+  e_value.list_value().push_back(Value(nullptr, std::move(e_value_scope)));
+
+  c_scope->SetValue("e", e_value, nullptr);
+
+  new_scope->SetValue("c", Value(nullptr, std::move(c_scope)), nullptr);
+
+  std::string expected(R"*({
+  "a": "foo",
+  "b": 6,
+  "c": {
+    "e": [
+      "bar",
+      {
+        "f": "baz"
+      }
+    ]
+  }
+})*");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(nullptr, std::move(new_scope)),
+                       Value(nullptr, "json"), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), expected);
+}
+
+TEST_F(OutputConversionTest, ValueEmpty) {
+  Err err;
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), Value(), Value(nullptr, ""), result, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "<void>");
+}
+
+TEST_F(OutputConversionTest, DefaultValue) {
+  Err err;
+  Value output(nullptr, "foo bar");
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(result.str(), "foo bar");
+}
+
+TEST_F(OutputConversionTest, DefaultListLines) {
+  Err err;
+  Value output(nullptr, Value::LIST);
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "foo"));
+  output.list_value().push_back(Value(nullptr, ""));
+  output.list_value().push_back(Value(nullptr, "bar"));
+  std::ostringstream result;
+  ConvertValueToOutput(settings(), output, Value(nullptr, ""), result, &err);
+
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ("\nfoo\n\nbar\n", result.str());
+}
+
+TEST_F(OutputConversionTest, ReverseString) {
+  Err err;
+  std::string input("foo bar");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "string"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "string"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseListLines) {
+  Err err;
+  std::string input("\nfoo\nbar\n\n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "list lines"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "list lines"),
+                       reverse, &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueString) {
+  Err err;
+  std::string input("\"str\"");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueInt) {
+  Err err;
+  std::string input("6");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueList) {
+  Err err;
+  std::string input("[\"a\", 5]");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueDict) {
+  Err err;
+  std::string input("  a = 5\n  b = \"foo\"\n  c = 7\n");
+  Value result = ConvertInputToValue(settings(), input, nullptr,
+                                     Value(nullptr, "scope"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "scope"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), input)
+      << "actual: " << reverse.str() << "expected:" << input;
+}
+
+TEST_F(OutputConversionTest, ReverseValueEmpty) {
+  Err err;
+  Value result = ConvertInputToValue(settings(), "", nullptr,
+                                     Value(nullptr, "value"), &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::ostringstream reverse;
+  ConvertValueToOutput(settings(), result, Value(nullptr, "value"), reverse,
+                       &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(reverse.str(), "");
+}
diff --git a/src/gn/output_file.cc b/src/gn/output_file.cc
new file mode 100644 (file)
index 0000000..ad25afa
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/output_file.h"
+
+#include "gn/filesystem_utils.h"
+#include "gn/source_file.h"
+
+OutputFile::OutputFile(std::string&& v) : value_(std::move(v)) {}
+
+OutputFile::OutputFile(const std::string& v) : value_(v) {}
+
+OutputFile::OutputFile(const BuildSettings* build_settings,
+                       const SourceFile& source_file)
+    : value_(RebasePath(source_file.value(),
+                        build_settings->build_dir(),
+                        build_settings->root_path_utf8())) {}
+
+SourceFile OutputFile::AsSourceFile(const BuildSettings* build_settings) const {
+  DCHECK(!value_.empty());
+  DCHECK(value_[value_.size() - 1] != '/');
+
+  std::string path = build_settings->build_dir().value();
+  path.append(value_);
+  return SourceFile(std::move(path));
+}
+
+SourceDir OutputFile::AsSourceDir(const BuildSettings* build_settings) const {
+  if (!value_.empty()) {
+    // Empty means the root build dir. Otherwise, we expect it to end in a
+    // slash.
+    DCHECK(value_[value_.size() - 1] == '/');
+  }
+  std::string path = build_settings->build_dir().value();
+  path.append(value_);
+  NormalizePath(&path);
+  return SourceDir(std::move(path));
+}
diff --git a/src/gn/output_file.h b/src/gn/output_file.h
new file mode 100644 (file)
index 0000000..d8d69ff
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_OUTPUT_FILE_H_
+#define TOOLS_GN_OUTPUT_FILE_H_
+
+#include <stddef.h>
+
+#include <string>
+
+class BuildSettings;
+class SourceDir;
+class SourceFile;
+
+// A simple wrapper around a string that indicates the string is a path
+// relative to the output directory.
+class OutputFile {
+ public:
+  OutputFile() = default;
+
+  explicit OutputFile(std::string&& v);
+  explicit OutputFile(const std::string& v);
+
+  OutputFile(const BuildSettings* build_settings,
+             const SourceFile& source_file);
+
+  std::string& value() { return value_; }
+  const std::string& value() const { return value_; }
+
+  // Converts to a SourceFile by prepending the build directory to the file.
+  // The *Dir version requires that the current OutputFile ends in a slash, and
+  // the *File version is the opposite.
+  SourceFile AsSourceFile(const BuildSettings* build_settings) const;
+  SourceDir AsSourceDir(const BuildSettings* build_settings) const;
+
+  bool operator==(const OutputFile& other) const {
+    return value_ == other.value_;
+  }
+  bool operator!=(const OutputFile& other) const {
+    return value_ != other.value_;
+  }
+  bool operator<(const OutputFile& other) const {
+    return value_ < other.value_;
+  }
+
+ private:
+  std::string value_;
+};
+
+namespace std {
+
+template <>
+struct hash<OutputFile> {
+  std::size_t operator()(const OutputFile& v) const {
+    hash<std::string> h;
+    return h(v.value());
+  }
+};
+
+}  // namespace std
+
+#endif  // TOOLS_GN_OUTPUT_FILE_H_
diff --git a/src/gn/parse_node_value_adapter.cc b/src/gn/parse_node_value_adapter.cc
new file mode 100644 (file)
index 0000000..a8adb17
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/parse_node_value_adapter.h"
+
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+
+ParseNodeValueAdapter::ParseNodeValueAdapter() : ref_(nullptr) {}
+
+ParseNodeValueAdapter::~ParseNodeValueAdapter() = default;
+
+bool ParseNodeValueAdapter::Init(Scope* scope,
+                                 const ParseNode* node,
+                                 Err* err) {
+  const IdentifierNode* identifier = node->AsIdentifier();
+  if (identifier) {
+    ref_ = scope->GetValue(identifier->value().value(), true);
+    if (!ref_) {
+      identifier->MakeErrorDescribing("Undefined identifier");
+      return false;
+    }
+    return true;
+  }
+
+  temporary_ = node->Execute(scope, err);
+  return !err->has_error();
+}
+
+bool ParseNodeValueAdapter::InitForType(Scope* scope,
+                                        const ParseNode* node,
+                                        Value::Type type,
+                                        Err* err) {
+  if (!Init(scope, node, err))
+    return false;
+  if (get().VerifyTypeIs(type, err))
+    return true;
+
+  // Fix up the error range (see class comment in the header file) to be the
+  // identifier node rather than the original value.
+  *err = Err(node, err->message(), err->help_text());
+  return false;
+}
diff --git a/src/gn/parse_node_value_adapter.h b/src/gn/parse_node_value_adapter.h
new file mode 100644 (file)
index 0000000..e724bb6
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_PARSE_NODE_VALUE_ADAPTER_H_
+#define TOOLS_GN_PARSE_NODE_VALUE_ADAPTER_H_
+
+#include "base/macros.h"
+#include "gn/value.h"
+
+class ParseNode;
+
+// Provides a means to convert a parse node to a value without causing a copy
+// in the common case of an "Identifier" node. Normally to get a value from a
+// parse node you have to call Execute(), and when an identifier is executed
+// it just returns the current value of itself as a copy. But some variables
+// are very large (lists of many strings for example).
+//
+// The reason you might not want to do this is that in the case of an
+// identifier where the copy is optimized away, the origin will still be the
+// original value. The result can be confusing because it will reference the
+// original value rather than the place where the value was dereferenced, e.g.
+// for a function call. The InitForType() function will verify type information
+// and will fix up the origin so it's not confusing.
+class ParseNodeValueAdapter {
+ public:
+  ParseNodeValueAdapter();
+  ~ParseNodeValueAdapter();
+
+  const Value& get() {
+    if (ref_)
+      return *ref_;
+    return temporary_;
+  }
+
+  // Initializes the adapter for the result of the given expression. Returns
+  // truen on success.
+  bool Init(Scope* scope, const ParseNode* node, Err* err);
+
+  // Like Init() but additionally verifies that the type of the result matches.
+  bool InitForType(Scope* scope,
+                   const ParseNode* node,
+                   Value::Type type,
+                   Err* err);
+
+ private:
+  // Holds either a reference to an existing item, or a temporary as a copy.
+  // If ref is non-null, it's valid, otherwise the temporary is used.
+  const Value* ref_;
+  Value temporary_;
+
+  DISALLOW_COPY_AND_ASSIGN(ParseNodeValueAdapter);
+};
+
+#endif  // TOOLS_GN_PARSE_NODE_VALUE_ADAPTER_H_
diff --git a/src/gn/parse_tree.cc b/src/gn/parse_tree.cc
new file mode 100644 (file)
index 0000000..9ab28b5
--- /dev/null
@@ -0,0 +1,1257 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/parse_tree.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include "base/json/string_escape.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "gn/functions.h"
+#include "gn/operators.h"
+#include "gn/scope.h"
+#include "gn/string_utils.h"
+
+// Dictionary keys used for JSON-formatted tree dump.
+const char kJsonNodeChild[] = "child";
+const char kJsonNodeType[] = "type";
+const char kJsonNodeValue[] = "value";
+const char kJsonBeforeComment[] = "before_comment";
+const char kJsonSuffixComment[] = "suffix_comment";
+const char kJsonAfterComment[] = "after_comment";
+const char kJsonLocation[] = "location";
+const char kJsonLocationBeginLine[] = "begin_line";
+const char kJsonLocationBeginColumn[] = "begin_column";
+const char kJsonLocationEndLine[] = "end_line";
+const char kJsonLocationEndColumn[] = "end_column";
+
+// Used by Block and List.
+const char kJsonBeginToken[] = "begin_token";
+const char kJsonEnd[] = "end";
+
+namespace {
+
+enum DepsCategory {
+  DEPS_CATEGORY_LOCAL,
+  DEPS_CATEGORY_RELATIVE,
+  DEPS_CATEGORY_ABSOLUTE,
+  DEPS_CATEGORY_OTHER,
+};
+
+DepsCategory GetDepsCategory(std::string_view deps) {
+  if (deps.length() < 2 || deps[0] != '"' || deps[deps.size() - 1] != '"')
+    return DEPS_CATEGORY_OTHER;
+
+  if (deps[1] == ':')
+    return DEPS_CATEGORY_LOCAL;
+
+  if (deps[1] == '/')
+    return DEPS_CATEGORY_ABSOLUTE;
+
+  return DEPS_CATEGORY_RELATIVE;
+}
+
+std::tuple<std::string_view, std::string_view> SplitAtFirst(
+    std::string_view str,
+    char c) {
+  if (!base::StartsWith(str, "\"", base::CompareCase::SENSITIVE) ||
+      !base::EndsWith(str, "\"", base::CompareCase::SENSITIVE))
+    return std::make_tuple(str, std::string_view());
+
+  str = str.substr(1, str.length() - 2);
+  size_t index_of_first = str.find(c);
+  return std::make_tuple(str.substr(0, index_of_first),
+                         index_of_first != std::string_view::npos
+                             ? str.substr(index_of_first + 1)
+                             : std::string_view());
+}
+
+bool IsSortRangeSeparator(const ParseNode* node, const ParseNode* prev) {
+  // If it's a block comment, or has an attached comment with a blank line
+  // before it, then we break the range at this point.
+  return node->AsBlockComment() != nullptr ||
+         (prev && node->comments() && !node->comments()->before().empty() &&
+          (node->GetRange().begin().line_number() >
+           prev->GetRange().end().line_number() +
+               static_cast<int>(node->comments()->before().size() + 1)));
+}
+
+std::string_view GetStringRepresentation(const ParseNode* node) {
+  DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor());
+  if (node->AsLiteral())
+    return node->AsLiteral()->value().value();
+  else if (node->AsIdentifier())
+    return node->AsIdentifier()->value().value();
+  else if (node->AsAccessor())
+    return node->AsAccessor()->base().value();
+  return std::string_view();
+}
+
+void AddLocationJSONNodes(base::Value* dict, LocationRange location) {
+  base::Value loc(base::Value::Type::DICTIONARY);
+  loc.SetKey(kJsonLocationBeginLine,
+             base::Value(location.begin().line_number()));
+  loc.SetKey(kJsonLocationBeginColumn,
+             base::Value(location.begin().column_number()));
+  loc.SetKey(kJsonLocationEndLine, base::Value(location.end().line_number()));
+  loc.SetKey(kJsonLocationEndColumn,
+             base::Value(location.end().column_number()));
+  dict->SetKey(kJsonLocation, std::move(loc));
+}
+
+Location GetBeginLocationFromJSON(const base::Value& value) {
+  int line =
+      value.FindKey(kJsonLocation)->FindKey(kJsonLocationBeginLine)->GetInt();
+  int column =
+      value.FindKey(kJsonLocation)->FindKey(kJsonLocationBeginColumn)->GetInt();
+  return Location(nullptr, line, column, 0);
+}
+
+void GetCommentsFromJSON(ParseNode* node, const base::Value& value) {
+  Comments* comments = node->comments_mutable();
+
+  Location loc = GetBeginLocationFromJSON(value);
+
+  auto loc_for = [&loc](int line) {
+    return Location(nullptr, loc.line_number() + line, loc.column_number(), 0);
+  };
+
+  if (value.FindKey(kJsonBeforeComment)) {
+    int line = 0;
+    for (const auto& c : value.FindKey(kJsonBeforeComment)->GetList()) {
+      comments->append_before(
+          Token::ClassifyAndMake(loc_for(line), c.GetString()));
+      ++line;
+    }
+  }
+
+  if (value.FindKey(kJsonSuffixComment)) {
+    int line = 0;
+    for (const auto& c : value.FindKey(kJsonSuffixComment)->GetList()) {
+      comments->append_suffix(
+          Token::ClassifyAndMake(loc_for(line), c.GetString()));
+      ++line;
+    }
+  }
+
+  if (value.FindKey(kJsonAfterComment)) {
+    int line = 0;
+    for (const auto& c : value.FindKey(kJsonAfterComment)->GetList()) {
+      comments->append_after(
+          Token::ClassifyAndMake(loc_for(line), c.GetString()));
+      ++line;
+    }
+  }
+}
+
+Token TokenFromValue(const base::Value& value) {
+  return Token::ClassifyAndMake(GetBeginLocationFromJSON(value),
+                                value.FindKey(kJsonNodeValue)->GetString());
+}
+
+}  // namespace
+
+Comments::Comments() = default;
+
+Comments::~Comments() = default;
+
+void Comments::ReverseSuffix() {
+  for (int i = 0, j = static_cast<int>(suffix_.size() - 1); i < j; ++i, --j)
+    std::swap(suffix_[i], suffix_[j]);
+}
+
+ParseNode::ParseNode() = default;
+
+ParseNode::~ParseNode() = default;
+
+const AccessorNode* ParseNode::AsAccessor() const {
+  return nullptr;
+}
+const BinaryOpNode* ParseNode::AsBinaryOp() const {
+  return nullptr;
+}
+const BlockCommentNode* ParseNode::AsBlockComment() const {
+  return nullptr;
+}
+const BlockNode* ParseNode::AsBlock() const {
+  return nullptr;
+}
+const ConditionNode* ParseNode::AsCondition() const {
+  return nullptr;
+}
+const EndNode* ParseNode::AsEnd() const {
+  return nullptr;
+}
+const FunctionCallNode* ParseNode::AsFunctionCall() const {
+  return nullptr;
+}
+const IdentifierNode* ParseNode::AsIdentifier() const {
+  return nullptr;
+}
+const ListNode* ParseNode::AsList() const {
+  return nullptr;
+}
+const LiteralNode* ParseNode::AsLiteral() const {
+  return nullptr;
+}
+const UnaryOpNode* ParseNode::AsUnaryOp() const {
+  return nullptr;
+}
+
+Comments* ParseNode::comments_mutable() {
+  if (!comments_)
+    comments_ = std::make_unique<Comments>();
+  return comments_.get();
+}
+
+base::Value ParseNode::CreateJSONNode(const char* type,
+                                      LocationRange location) const {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(kJsonNodeType, base::Value(type));
+  AddLocationJSONNodes(&dict, location);
+  AddCommentsJSONNodes(&dict);
+  return dict;
+}
+
+base::Value ParseNode::CreateJSONNode(const char* type,
+                                      const std::string_view& value,
+                                      LocationRange location) const {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(kJsonNodeType, base::Value(type));
+  dict.SetKey(kJsonNodeValue, base::Value(value));
+  AddLocationJSONNodes(&dict, location);
+  AddCommentsJSONNodes(&dict);
+  return dict;
+}
+
+void ParseNode::AddCommentsJSONNodes(base::Value* out_value) const {
+  if (comments_) {
+    if (comments_->before().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->before())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonBeforeComment, std::move(comment_values));
+    }
+    if (comments_->suffix().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->suffix())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonSuffixComment, std::move(comment_values));
+    }
+    if (comments_->after().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->after())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonAfterComment, std::move(comment_values));
+    }
+  }
+}
+
+// static
+std::unique_ptr<ParseNode> ParseNode::BuildFromJSON(const base::Value& value) {
+  const std::string& str_type = value.FindKey(kJsonNodeType)->GetString();
+
+#define RETURN_IF_MATCHES_NAME(t)     \
+  if (str_type == t::kDumpNodeName) { \
+    return t::NewFromJSON(value);     \
+  }
+
+  RETURN_IF_MATCHES_NAME(AccessorNode);
+  RETURN_IF_MATCHES_NAME(BinaryOpNode);
+  RETURN_IF_MATCHES_NAME(BlockCommentNode);
+  RETURN_IF_MATCHES_NAME(BlockNode);
+  RETURN_IF_MATCHES_NAME(ConditionNode);
+  RETURN_IF_MATCHES_NAME(EndNode);
+  RETURN_IF_MATCHES_NAME(FunctionCallNode);
+  RETURN_IF_MATCHES_NAME(IdentifierNode);
+  RETURN_IF_MATCHES_NAME(ListNode);
+  RETURN_IF_MATCHES_NAME(LiteralNode);
+  RETURN_IF_MATCHES_NAME(UnaryOpNode);
+
+#undef RETURN_IF_MATCHES_NAME
+
+  NOTREACHED() << str_type;
+  return std::unique_ptr<ParseNode>();
+}
+
+// AccessorNode ---------------------------------------------------------------
+
+AccessorNode::AccessorNode() = default;
+
+AccessorNode::~AccessorNode() = default;
+
+const AccessorNode* AccessorNode::AsAccessor() const {
+  return this;
+}
+
+Value AccessorNode::Execute(Scope* scope, Err* err) const {
+  if (subscript_)
+    return ExecuteSubscriptAccess(scope, err);
+  else if (member_)
+    return ExecuteScopeAccess(scope, err);
+  NOTREACHED();
+  return Value();
+}
+
+LocationRange AccessorNode::GetRange() const {
+  if (subscript_)
+    return LocationRange(base_.location(), subscript_->GetRange().end());
+  else if (member_)
+    return LocationRange(base_.location(), member_->GetRange().end());
+  NOTREACHED();
+  return LocationRange();
+}
+
+Err AccessorNode::MakeErrorDescribing(const std::string& msg,
+                                      const std::string& help) const {
+  return Err(GetRange(), msg, help);
+}
+
+base::Value AccessorNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode(kDumpNodeName, base_.value(), GetRange()));
+  base::Value child(base::Value::Type::LIST);
+  if (subscript_) {
+    child.GetList().push_back(subscript_->GetJSONNode());
+    dict.SetKey(kDumpAccessorKind, base::Value(kDumpAccessorKindSubscript));
+  } else if (member_) {
+    child.GetList().push_back(member_->GetJSONNode());
+    dict.SetKey(kDumpAccessorKind, base::Value(kDumpAccessorKindMember));
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
+}
+
+#define DECLARE_CHILD_AS_LIST_OR_FAIL()                     \
+  const base::Value* child = value.FindKey(kJsonNodeChild); \
+  if (!child || !child->is_list()) {                        \
+    return nullptr;                                         \
+  }
+
+// static
+std::unique_ptr<AccessorNode> AccessorNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<AccessorNode>();
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  ret->base_ = TokenFromValue(value);
+  const base::Value::ListStorage& children = child->GetList();
+  const std::string& kind = value.FindKey(kDumpAccessorKind)->GetString();
+  if (kind == kDumpAccessorKindSubscript) {
+    ret->subscript_ = ParseNode::BuildFromJSON(children[0]);
+  } else if (kind == kDumpAccessorKindMember) {
+    ret->member_ = IdentifierNode::NewFromJSON(children[0]);
+  }
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+Value AccessorNode::ExecuteSubscriptAccess(Scope* scope, Err* err) const {
+  const Value* base_value = scope->GetValue(base_.value(), true);
+  if (!base_value) {
+    *err = MakeErrorDescribing("Undefined identifier.");
+    return Value();
+  }
+  if (base_value->type() == Value::LIST) {
+    return ExecuteArrayAccess(scope, base_value, err);
+  } else if (base_value->type() == Value::SCOPE) {
+    return ExecuteScopeSubscriptAccess(scope, base_value, err);
+  } else {
+    *err = MakeErrorDescribing(
+        std::string("Expecting either a list or a scope for subscript, got ") +
+        Value::DescribeType(base_value->type()) + ".");
+    return Value();
+  }
+}
+
+Value AccessorNode::ExecuteArrayAccess(Scope* scope,
+                                       const Value* base_value,
+                                       Err* err) const {
+  size_t index = 0;
+  if (!ComputeAndValidateListIndex(scope, base_value->list_value().size(),
+                                   &index, err))
+    return Value();
+  return base_value->list_value()[index];
+}
+
+Value AccessorNode::ExecuteScopeSubscriptAccess(Scope* scope,
+                                                const Value* base_value,
+                                                Err* err) const {
+  Value key_value = subscript_->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  if (!key_value.VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const Value* result =
+      base_value->scope_value()->GetValue(key_value.string_value());
+  if (!result) {
+    *err =
+        Err(subscript_.get(), "No value named \"" + key_value.string_value() +
+                                  "\" in scope \"" + base_.value() + "\"");
+    return Value();
+  }
+  return *result;
+}
+
+Value AccessorNode::ExecuteScopeAccess(Scope* scope, Err* err) const {
+  // We jump through some hoops here since ideally a.b will count "b" as
+  // accessed in the given scope. The value "a" might be in some normal nested
+  // scope and we can modify it, but it might also be inherited from the
+  // readonly root scope and we can't do used variable tracking on it. (It's
+  // not legal to const cast it away since the root scope will be in readonly
+  // mode and being accessed from multiple threads without locking.) So this
+  // code handles both cases.
+  const Value* result = nullptr;
+
+  // Look up the value in the scope named by "base_".
+  Value* mutable_base_value =
+      scope->GetMutableValue(base_.value(), Scope::SEARCH_NESTED, true);
+  if (mutable_base_value) {
+    // Common case: base value is mutable so we can track variable accesses
+    // for unused value warnings.
+    if (!mutable_base_value->VerifyTypeIs(Value::SCOPE, err))
+      return Value();
+    result = mutable_base_value->scope_value()->GetValue(
+        member_->value().value(), true);
+  } else {
+    // Fall back to see if the value is on a read-only scope.
+    const Value* const_base_value = scope->GetValue(base_.value(), true);
+    if (const_base_value) {
+      // Read only value, don't try to mark the value access as a "used" one.
+      if (!const_base_value->VerifyTypeIs(Value::SCOPE, err))
+        return Value();
+      result =
+          const_base_value->scope_value()->GetValue(member_->value().value());
+    } else {
+      *err = Err(base_, "Undefined identifier.");
+      return Value();
+    }
+  }
+
+  if (!result) {
+    *err = Err(member_.get(), "No value named \"" + member_->value().value() +
+                                  "\" in scope \"" + base_.value() + "\"");
+    return Value();
+  }
+  return *result;
+}
+
+void AccessorNode::SetNewLocation(int line_number) {
+  Location old = base_.location();
+  base_.set_location(
+      Location(old.file(), line_number, old.column_number(), old.byte()));
+}
+
+bool AccessorNode::ComputeAndValidateListIndex(Scope* scope,
+                                               size_t max_len,
+                                               size_t* computed_index,
+                                               Err* err) const {
+  Value index_value = subscript_->Execute(scope, err);
+  if (err->has_error())
+    return false;
+  if (!index_value.VerifyTypeIs(Value::INTEGER, err))
+    return false;
+
+  int64_t index_int = index_value.int_value();
+  if (index_int < 0) {
+    *err = Err(subscript_->GetRange(), "Negative array subscript.",
+               "You gave me " + base::Int64ToString(index_int) + ".");
+    return false;
+  }
+  if (max_len == 0) {
+    *err = Err(subscript_->GetRange(), "Array subscript out of range.",
+               "You gave me " + base::Int64ToString(index_int) + " but the " +
+                   "array has no elements.");
+    return false;
+  }
+  size_t index_sizet = static_cast<size_t>(index_int);
+  if (index_sizet >= max_len) {
+    *err = Err(subscript_->GetRange(), "Array subscript out of range.",
+               "You gave me " + base::Int64ToString(index_int) +
+                   " but I was expecting something from 0 to " +
+                   base::NumberToString(max_len - 1) + ", inclusive.");
+    return false;
+  }
+
+  *computed_index = index_sizet;
+  return true;
+}
+
+// BinaryOpNode ---------------------------------------------------------------
+
+BinaryOpNode::BinaryOpNode() = default;
+
+BinaryOpNode::~BinaryOpNode() = default;
+
+const BinaryOpNode* BinaryOpNode::AsBinaryOp() const {
+  return this;
+}
+
+Value BinaryOpNode::Execute(Scope* scope, Err* err) const {
+  return ExecuteBinaryOperator(scope, this, left_.get(), right_.get(), err);
+}
+
+LocationRange BinaryOpNode::GetRange() const {
+  return left_->GetRange().Union(right_->GetRange());
+}
+
+Err BinaryOpNode::MakeErrorDescribing(const std::string& msg,
+                                      const std::string& help) const {
+  return Err(op_, msg, help);
+}
+
+base::Value BinaryOpNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode(kDumpNodeName, op_.value(), GetRange()));
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(left_->GetJSONNode());
+  child.GetList().push_back(right_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
+}
+
+// static
+std::unique_ptr<BinaryOpNode> BinaryOpNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<BinaryOpNode>();
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  const base::Value::ListStorage& children = child->GetList();
+  ret->left_ = ParseNode::BuildFromJSON(children[0]);
+  ret->right_ = ParseNode::BuildFromJSON(children[1]);
+  ret->op_ = TokenFromValue(value);
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+// BlockNode ------------------------------------------------------------------
+
+BlockNode::BlockNode(ResultMode result_mode) : result_mode_(result_mode) {}
+
+BlockNode::~BlockNode() = default;
+
+const BlockNode* BlockNode::AsBlock() const {
+  return this;
+}
+
+Value BlockNode::Execute(Scope* enclosing_scope, Err* err) const {
+  std::unique_ptr<Scope> nested_scope;  // May be null.
+
+  Scope* execution_scope;  // Either the enclosing_scope or nested_scope.
+  if (result_mode_ == RETURNS_SCOPE) {
+    // Create a nested scope to save the values for returning.
+    nested_scope = std::make_unique<Scope>(enclosing_scope);
+    execution_scope = nested_scope.get();
+  } else {
+    // Use the enclosing scope. Modifications will go into this also (for
+    // example, if conditions and loops).
+    execution_scope = enclosing_scope;
+  }
+
+  for (size_t i = 0; i < statements_.size() && !err->has_error(); i++) {
+    // Check for trying to execute things with no side effects in a block.
+    //
+    // A BlockNode here means that somebody has a free-floating { }.
+    // Technically this can have side effects since it could generated targets,
+    // but we don't want to allow this since it creates ambiguity when
+    // immediately following a function call that takes no block. By not
+    // allowing free-floating blocks that aren't passed anywhere or assigned to
+    // anything, this ambiguity is resolved.
+    const ParseNode* cur = statements_[i].get();
+    if (cur->AsList() || cur->AsLiteral() || cur->AsUnaryOp() ||
+        cur->AsIdentifier() || cur->AsBlock()) {
+      *err = cur->MakeErrorDescribing(
+          "This statement has no effect.",
+          "Either delete it or do something with the result.");
+      return Value();
+    }
+    cur->Execute(execution_scope, err);
+  }
+
+  if (result_mode_ == RETURNS_SCOPE) {
+    // Clear the reference to the containing scope. This will be passed in
+    // a value whose lifetime will not be related to the enclosing_scope passed
+    // to this function.
+    nested_scope->DetachFromContaining();
+    return Value(this, std::move(nested_scope));
+  }
+  return Value();
+}
+
+LocationRange BlockNode::GetRange() const {
+  if (begin_token_.type() != Token::INVALID &&
+      end_->value().type() != Token::INVALID) {
+    return begin_token_.range().Union(end_->value().range());
+  } else if (!statements_.empty()) {
+    return statements_[0]->GetRange().Union(
+        statements_[statements_.size() - 1]->GetRange());
+  }
+  return LocationRange();
+}
+
+Err BlockNode::MakeErrorDescribing(const std::string& msg,
+                                   const std::string& help) const {
+  return Err(GetRange(), msg, help);
+}
+
+base::Value BlockNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode(kDumpNodeName, GetRange()));
+  base::Value statements(base::Value::Type::LIST);
+  for (const auto& statement : statements_)
+    statements.GetList().push_back(statement->GetJSONNode());
+  if (end_)
+    dict.SetKey(kJsonEnd, end_->GetJSONNode());
+
+  dict.SetKey(kJsonNodeChild, std::move(statements));
+
+  if (result_mode_ == BlockNode::RETURNS_SCOPE) {
+    dict.SetKey(kDumpResultMode, base::Value(kDumpResultModeReturnsScope));
+  } else if (result_mode_ == BlockNode::DISCARDS_RESULT) {
+    dict.SetKey(kDumpResultMode, base::Value(kDumpResultModeDiscardsResult));
+  } else {
+    NOTREACHED();
+  }
+
+  dict.SetKey(kJsonBeginToken, base::Value(begin_token_.value()));
+
+  return dict;
+}
+
+// static
+std::unique_ptr<BlockNode> BlockNode::NewFromJSON(const base::Value& value) {
+  const std::string& result_mode = value.FindKey(kDumpResultMode)->GetString();
+  std::unique_ptr<BlockNode> ret;
+
+  if (result_mode == kDumpResultModeReturnsScope) {
+    ret.reset(new BlockNode(BlockNode::RETURNS_SCOPE));
+  } else if (result_mode == kDumpResultModeDiscardsResult) {
+    ret.reset(new BlockNode(BlockNode::DISCARDS_RESULT));
+  } else {
+    NOTREACHED();
+  }
+
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  for (const auto& elem : child->GetList()) {
+    ret->statements_.push_back(ParseNode::BuildFromJSON(elem));
+  }
+
+  ret->begin_token_ =
+      Token::ClassifyAndMake(GetBeginLocationFromJSON(value),
+                             value.FindKey(kJsonBeginToken)->GetString());
+  if (value.FindKey(kJsonEnd)) {
+    ret->end_ = EndNode::NewFromJSON(*value.FindKey(kJsonEnd));
+  }
+
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+// ConditionNode --------------------------------------------------------------
+
+ConditionNode::ConditionNode() = default;
+
+ConditionNode::~ConditionNode() = default;
+
+const ConditionNode* ConditionNode::AsCondition() const {
+  return this;
+}
+
+Value ConditionNode::Execute(Scope* scope, Err* err) const {
+  Value condition_result = condition_->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  if (condition_result.type() != Value::BOOLEAN) {
+    *err = condition_->MakeErrorDescribing(
+        "Condition does not evaluate to a boolean value.",
+        std::string("This is a value of type \"") +
+            Value::DescribeType(condition_result.type()) + "\" instead.");
+    err->AppendRange(if_token_.range());
+    return Value();
+  }
+
+  if (condition_result.boolean_value()) {
+    if_true_->Execute(scope, err);
+  } else if (if_false_) {
+    // The else block is optional.
+    if_false_->Execute(scope, err);
+  }
+
+  return Value();
+}
+
+LocationRange ConditionNode::GetRange() const {
+  if (if_false_)
+    return if_token_.range().Union(if_false_->GetRange());
+  return if_token_.range().Union(if_true_->GetRange());
+}
+
+Err ConditionNode::MakeErrorDescribing(const std::string& msg,
+                                       const std::string& help) const {
+  return Err(if_token_, msg, help);
+}
+
+base::Value ConditionNode::GetJSONNode() const {
+  base::Value dict = CreateJSONNode(kDumpNodeName, GetRange());
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(condition_->GetJSONNode());
+  child.GetList().push_back(if_true_->GetJSONNode());
+  if (if_false_) {
+    child.GetList().push_back(if_false_->GetJSONNode());
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
+}
+
+// static
+std::unique_ptr<ConditionNode> ConditionNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<ConditionNode>();
+
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  const base::Value::ListStorage& children = child->GetList();
+
+  ret->if_token_ =
+      Token::ClassifyAndMake(GetBeginLocationFromJSON(value), "if");
+  ret->condition_ = ParseNode::BuildFromJSON(children[0]);
+  ret->if_true_ = BlockNode::NewFromJSON(children[1]);
+  if (children.size() > 2) {
+    ret->if_false_ = ParseNode::BuildFromJSON(children[2]);
+  }
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+// FunctionCallNode -----------------------------------------------------------
+
+FunctionCallNode::FunctionCallNode() = default;
+
+FunctionCallNode::~FunctionCallNode() = default;
+
+const FunctionCallNode* FunctionCallNode::AsFunctionCall() const {
+  return this;
+}
+
+Value FunctionCallNode::Execute(Scope* scope, Err* err) const {
+  return functions::RunFunction(scope, this, args_.get(), block_.get(), err);
+}
+
+LocationRange FunctionCallNode::GetRange() const {
+  if (function_.type() == Token::INVALID)
+    return LocationRange();  // This will be null in some tests.
+  if (block_)
+    return function_.range().Union(block_->GetRange());
+  return function_.range().Union(args_->GetRange());
+}
+
+Err FunctionCallNode::MakeErrorDescribing(const std::string& msg,
+                                          const std::string& help) const {
+  return Err(function_, msg, help);
+}
+
+base::Value FunctionCallNode::GetJSONNode() const {
+  base::Value dict =
+      CreateJSONNode(kDumpNodeName, function_.value(), GetRange());
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(args_->GetJSONNode());
+  if (block_) {
+    child.GetList().push_back(block_->GetJSONNode());
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
+}
+
+// static
+std::unique_ptr<FunctionCallNode> FunctionCallNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<FunctionCallNode>();
+
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  const base::Value::ListStorage& children = child->GetList();
+  ret->function_ = TokenFromValue(value);
+  ret->args_ = ListNode::NewFromJSON(children[0]);
+  if (children.size() > 1)
+    ret->block_ = BlockNode::NewFromJSON(children[1]);
+
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+void FunctionCallNode::SetNewLocation(int line_number) {
+  Location func_old_loc = function_.location();
+  Location func_new_loc =
+      Location(func_old_loc.file(), line_number, func_old_loc.column_number(),
+               func_old_loc.byte());
+  function_.set_location(func_new_loc);
+
+  Location args_old_loc = args_->Begin().location();
+  Location args_new_loc =
+      Location(args_old_loc.file(), line_number, args_old_loc.column_number(),
+               args_old_loc.byte());
+  const_cast<Token&>(args_->Begin()).set_location(args_new_loc);
+  const_cast<Token&>(args_->End()->value()).set_location(args_new_loc);
+}
+
+// IdentifierNode --------------------------------------------------------------
+
+IdentifierNode::IdentifierNode() = default;
+
+IdentifierNode::IdentifierNode(const Token& token) : value_(token) {}
+
+IdentifierNode::~IdentifierNode() = default;
+
+const IdentifierNode* IdentifierNode::AsIdentifier() const {
+  return this;
+}
+
+Value IdentifierNode::Execute(Scope* scope, Err* err) const {
+  const Scope* found_in_scope = nullptr;
+  const Value* value =
+      scope->GetValueWithScope(value_.value(), true, &found_in_scope);
+  Value result;
+  if (!value) {
+    *err = MakeErrorDescribing("Undefined identifier");
+    return result;
+  }
+
+  if (!EnsureNotReadingFromSameDeclareArgs(this, scope, found_in_scope, err))
+    return result;
+
+  result = *value;
+  result.set_origin(this);
+  return result;
+}
+
+LocationRange IdentifierNode::GetRange() const {
+  return value_.range();
+}
+
+Err IdentifierNode::MakeErrorDescribing(const std::string& msg,
+                                        const std::string& help) const {
+  return Err(value_, msg, help);
+}
+
+base::Value IdentifierNode::GetJSONNode() const {
+  return CreateJSONNode(kDumpNodeName, value_.value(), GetRange());
+}
+
+// static
+std::unique_ptr<IdentifierNode> IdentifierNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<IdentifierNode>();
+  ret->set_value(TokenFromValue(value));
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+void IdentifierNode::SetNewLocation(int line_number) {
+  Location old = value_.location();
+  value_.set_location(
+      Location(old.file(), line_number, old.column_number(), old.byte()));
+}
+
+// ListNode -------------------------------------------------------------------
+
+ListNode::ListNode() {}
+
+ListNode::~ListNode() = default;
+
+const ListNode* ListNode::AsList() const {
+  return this;
+}
+
+Value ListNode::Execute(Scope* scope, Err* err) const {
+  Value result_value(this, Value::LIST);
+  std::vector<Value>& results = result_value.list_value();
+  results.reserve(contents_.size());
+
+  for (const auto& cur : contents_) {
+    if (cur->AsBlockComment())
+      continue;
+    results.push_back(cur->Execute(scope, err));
+    if (err->has_error())
+      return Value();
+    if (results.back().type() == Value::NONE) {
+      *err = cur->MakeErrorDescribing("This does not evaluate to a value.",
+                                      "I can't do something with nothing.");
+      return Value();
+    }
+  }
+  return result_value;
+}
+
+LocationRange ListNode::GetRange() const {
+  return LocationRange(begin_token_.location(), end_->value().location());
+}
+
+Err ListNode::MakeErrorDescribing(const std::string& msg,
+                                  const std::string& help) const {
+  return Err(begin_token_, msg, help);
+}
+
+base::Value ListNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode(kDumpNodeName, GetRange()));
+  base::Value child(base::Value::Type::LIST);
+  for (const auto& cur : contents_) {
+    child.GetList().push_back(cur->GetJSONNode());
+  }
+  if (end_)
+    dict.SetKey(kJsonEnd, end_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  dict.SetKey(kJsonBeginToken, base::Value(begin_token_.value()));
+  return dict;
+}
+
+// static
+std::unique_ptr<ListNode> ListNode::NewFromJSON(const base::Value& value) {
+  auto ret = std::make_unique<ListNode>();
+
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  for (const auto& elem : child->GetList()) {
+    ret->contents_.push_back(ParseNode::BuildFromJSON(elem));
+  }
+  ret->begin_token_ =
+      Token::ClassifyAndMake(GetBeginLocationFromJSON(value),
+                             value.FindKey(kJsonBeginToken)->GetString());
+  if (value.FindKey(kJsonEnd)) {
+    ret->end_ = EndNode::NewFromJSON(*value.FindKey(kJsonEnd));
+  }
+
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+template <typename Comparator>
+void ListNode::SortList(Comparator comparator) {
+  // Partitions first on BlockCommentNodes and sorts each partition separately.
+  for (auto sr : GetSortRanges()) {
+    bool skip = false;
+    for (size_t i = sr.begin; i != sr.end; ++i) {
+      // Bails out if any of the nodes are unsupported.
+      const ParseNode* node = contents_[i].get();
+      if (!node->AsLiteral() && !node->AsIdentifier() && !node->AsAccessor()) {
+        skip = true;
+        continue;
+      }
+    }
+    if (skip)
+      continue;
+    // Save the original line number so that we can re-assign ranges. We assume
+    // they're contiguous lines because GetSortRanges() does so above. We need
+    // to re-assign these line numbers primiarily because `gn format` uses them
+    // to determine whether two nodes were initially separated by a blank line
+    // or not.
+    int start_line = contents_[sr.begin]->GetRange().begin().line_number();
+    const ParseNode* original_first = contents_[sr.begin].get();
+    std::sort(contents_.begin() + sr.begin, contents_.begin() + sr.end,
+              [&comparator](const std::unique_ptr<const ParseNode>& a,
+                            const std::unique_ptr<const ParseNode>& b) {
+                return comparator(a.get(), b.get());
+              });
+    // If the beginning of the range had before comments, and the first node
+    // moved during the sort, then move its comments to the new head of the
+    // range.
+    if (original_first->comments() &&
+        contents_[sr.begin].get() != original_first) {
+      for (const auto& hc : original_first->comments()->before()) {
+        const_cast<ParseNode*>(contents_[sr.begin].get())
+            ->comments_mutable()
+            ->append_before(hc);
+      }
+      const_cast<ParseNode*>(original_first)
+          ->comments_mutable()
+          ->clear_before();
+    }
+    const ParseNode* prev = nullptr;
+    for (size_t i = sr.begin; i != sr.end; ++i) {
+      const ParseNode* node = contents_[i].get();
+      DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor());
+      int line_number =
+          prev ? prev->GetRange().end().line_number() + 1 : start_line;
+      if (node->AsLiteral()) {
+        const_cast<LiteralNode*>(node->AsLiteral())
+            ->SetNewLocation(line_number);
+      } else if (node->AsIdentifier()) {
+        const_cast<IdentifierNode*>(node->AsIdentifier())
+            ->SetNewLocation(line_number);
+      } else if (node->AsAccessor()) {
+        const_cast<AccessorNode*>(node->AsAccessor())
+            ->SetNewLocation(line_number);
+      }
+      prev = node;
+    }
+  }
+}
+
+void ListNode::SortAsStringsList() {
+  // Sorts alphabetically.
+  SortList([](const ParseNode* a, const ParseNode* b) {
+    std::string_view astr = GetStringRepresentation(a);
+    std::string_view bstr = GetStringRepresentation(b);
+    return astr < bstr;
+  });
+}
+
+void ListNode::SortAsTargetsList() {
+  // Sorts first relative targets, then absolute, each group is sorted
+  // alphabetically.
+  SortList([](const ParseNode* a, const ParseNode* b) {
+    std::string_view astr = GetStringRepresentation(a);
+    std::string_view bstr = GetStringRepresentation(b);
+    return std::make_pair(GetDepsCategory(astr), SplitAtFirst(astr, ':')) <
+           std::make_pair(GetDepsCategory(bstr), SplitAtFirst(bstr, ':'));
+  });
+}
+
+// Breaks the ParseNodes of |contents| up by ranges that should be separately
+// sorted. In particular, we break at a block comment, or an item that has an
+// attached "before" comment and is separated by a blank line from the item
+// before it. The assumption is that both of these indicate a separate 'section'
+// of a sources block across which items should not be inter-sorted.
+std::vector<ListNode::SortRange> ListNode::GetSortRanges() const {
+  std::vector<SortRange> ranges;
+  const ParseNode* prev = nullptr;
+  size_t begin = 0;
+  for (size_t i = begin; i < contents_.size(); prev = contents_[i++].get()) {
+    if (IsSortRangeSeparator(contents_[i].get(), prev)) {
+      if (i > begin) {
+        ranges.push_back(SortRange(begin, i));
+        // If |i| is an item with an attached comment, then we start the next
+        // range at that point, because we want to include it in the sort.
+        // Otherwise, it's a block comment which we skip over entirely because
+        // we don't want to move or include it in the sort. The two cases are:
+        //
+        // sources = [
+        //   "a",
+        //   "b",
+        //
+        //   #
+        //   # This is a block comment.
+        //   #
+        //
+        //   "c",
+        //   "d",
+        // ]
+        //
+        // which contains 5 elements, and for which the ranges would be { [0,
+        // 2), [3, 5) } (notably excluding 2, the block comment), and:
+        //
+        // sources = [
+        //   "a",
+        //   "b",
+        //
+        //   # This is a header comment.
+        //   "c",
+        //   "d",
+        // ]
+        //
+        // which contains 4 elements, index 2 containing an attached 'before'
+        // comments, and the ranges should be { [0, 2), [2, 4) }.
+        if (!contents_[i]->AsBlockComment())
+          begin = i;
+        else
+          begin = i + 1;
+      } else {
+        // If it was a one item range, just skip over it.
+        begin = i + 1;
+      }
+    }
+  }
+  if (begin != contents_.size())
+    ranges.push_back(SortRange(begin, contents_.size()));
+  return ranges;
+}
+
+// LiteralNode -----------------------------------------------------------------
+
+LiteralNode::LiteralNode() = default;
+
+LiteralNode::LiteralNode(const Token& token) : value_(token) {}
+
+LiteralNode::~LiteralNode() = default;
+
+const LiteralNode* LiteralNode::AsLiteral() const {
+  return this;
+}
+
+Value LiteralNode::Execute(Scope* scope, Err* err) const {
+  switch (value_.type()) {
+    case Token::TRUE_TOKEN:
+      return Value(this, true);
+    case Token::FALSE_TOKEN:
+      return Value(this, false);
+    case Token::INTEGER: {
+      std::string_view s = value_.value();
+      if ((base::StartsWith(s, "0", base::CompareCase::SENSITIVE) &&
+           s.size() > 1) ||
+          base::StartsWith(s, "-0", base::CompareCase::SENSITIVE)) {
+        if (s == "-0")
+          *err = MakeErrorDescribing("Negative zero doesn't make sense");
+        else
+          *err = MakeErrorDescribing("Leading zeros not allowed");
+        return Value();
+      }
+      int64_t result_int;
+      if (!base::StringToInt64(s, &result_int)) {
+        *err = MakeErrorDescribing("This does not look like an integer");
+        return Value();
+      }
+      return Value(this, result_int);
+    }
+    case Token::STRING: {
+      Value v(this, Value::STRING);
+      ExpandStringLiteral(scope, value_, &v, err);
+      return v;
+    }
+    default:
+      NOTREACHED();
+      return Value();
+  }
+}
+
+LocationRange LiteralNode::GetRange() const {
+  return value_.range();
+}
+
+Err LiteralNode::MakeErrorDescribing(const std::string& msg,
+                                     const std::string& help) const {
+  return Err(value_, msg, help);
+}
+
+base::Value LiteralNode::GetJSONNode() const {
+  return CreateJSONNode(kDumpNodeName, value_.value(), GetRange());
+}
+
+// static
+std::unique_ptr<LiteralNode> LiteralNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<LiteralNode>();
+  ret->value_ = TokenFromValue(value);
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+void LiteralNode::SetNewLocation(int line_number) {
+  Location old = value_.location();
+  value_.set_location(
+      Location(old.file(), line_number, old.column_number(), old.byte()));
+}
+
+// UnaryOpNode ----------------------------------------------------------------
+
+UnaryOpNode::UnaryOpNode() = default;
+
+UnaryOpNode::~UnaryOpNode() = default;
+
+const UnaryOpNode* UnaryOpNode::AsUnaryOp() const {
+  return this;
+}
+
+Value UnaryOpNode::Execute(Scope* scope, Err* err) const {
+  Value operand_value = operand_->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  return ExecuteUnaryOperator(scope, this, operand_value, err);
+}
+
+LocationRange UnaryOpNode::GetRange() const {
+  return op_.range().Union(operand_->GetRange());
+}
+
+Err UnaryOpNode::MakeErrorDescribing(const std::string& msg,
+                                     const std::string& help) const {
+  return Err(op_, msg, help);
+}
+
+base::Value UnaryOpNode::GetJSONNode() const {
+  base::Value dict = CreateJSONNode(kDumpNodeName, op_.value(), GetRange());
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(operand_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
+}
+
+// static
+std::unique_ptr<UnaryOpNode> UnaryOpNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<UnaryOpNode>();
+  ret->op_ = TokenFromValue(value);
+  DECLARE_CHILD_AS_LIST_OR_FAIL();
+  ret->operand_ = ParseNode::BuildFromJSON(child->GetList()[0]);
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+// BlockCommentNode ------------------------------------------------------------
+
+BlockCommentNode::BlockCommentNode() = default;
+
+BlockCommentNode::~BlockCommentNode() = default;
+
+const BlockCommentNode* BlockCommentNode::AsBlockComment() const {
+  return this;
+}
+
+Value BlockCommentNode::Execute(Scope* scope, Err* err) const {
+  return Value();
+}
+
+LocationRange BlockCommentNode::GetRange() const {
+  return comment_.range();
+}
+
+Err BlockCommentNode::MakeErrorDescribing(const std::string& msg,
+                                          const std::string& help) const {
+  return Err(comment_, msg, help);
+}
+
+base::Value BlockCommentNode::GetJSONNode() const {
+  std::string escaped;
+  return CreateJSONNode(kDumpNodeName, comment_.value(), GetRange());
+}
+
+// static
+std::unique_ptr<BlockCommentNode> BlockCommentNode::NewFromJSON(
+    const base::Value& value) {
+  auto ret = std::make_unique<BlockCommentNode>();
+  ret->comment_ = Token(GetBeginLocationFromJSON(value), Token::BLOCK_COMMENT,
+                        value.FindKey(kJsonNodeValue)->GetString());
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
+
+// EndNode ---------------------------------------------------------------------
+
+EndNode::EndNode(const Token& token) : value_(token) {}
+
+EndNode::~EndNode() = default;
+
+const EndNode* EndNode::AsEnd() const {
+  return this;
+}
+
+Value EndNode::Execute(Scope* scope, Err* err) const {
+  return Value();
+}
+
+LocationRange EndNode::GetRange() const {
+  return value_.range();
+}
+
+Err EndNode::MakeErrorDescribing(const std::string& msg,
+                                 const std::string& help) const {
+  return Err(value_, msg, help);
+}
+
+base::Value EndNode::GetJSONNode() const {
+  return CreateJSONNode(kDumpNodeName, value_.value(), GetRange());
+}
+
+// static
+std::unique_ptr<EndNode> EndNode::NewFromJSON(const base::Value& value) {
+  auto ret = std::make_unique<EndNode>(TokenFromValue(value));
+  GetCommentsFromJSON(ret.get(), value);
+  return ret;
+}
diff --git a/src/gn/parse_tree.h b/src/gn/parse_tree.h
new file mode 100644 (file)
index 0000000..9ffacc2
--- /dev/null
@@ -0,0 +1,613 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_PARSE_TREE_H_
+#define TOOLS_GN_PARSE_TREE_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "gn/err.h"
+#include "gn/token.h"
+#include "gn/value.h"
+
+class AccessorNode;
+class BinaryOpNode;
+class BlockCommentNode;
+class BlockNode;
+class ConditionNode;
+class EndNode;
+class FunctionCallNode;
+class IdentifierNode;
+class ListNode;
+class LiteralNode;
+class Scope;
+class UnaryOpNode;
+
+// Dictionary keys used for JSON-formatted tree dump.
+extern const char kJsonNodeChild[];
+extern const char kJsonNodeType[];
+extern const char kJsonNodeValue[];
+extern const char kJsonBeforeComment[];
+extern const char kJsonSuffixComment[];
+extern const char kJsonAfterComment[];
+
+class Comments {
+ public:
+  Comments();
+  virtual ~Comments();
+
+  const std::vector<Token>& before() const { return before_; }
+  void append_before(Token c) { before_.push_back(c); }
+  void clear_before() { before_.clear(); }
+
+  const std::vector<Token>& suffix() const { return suffix_; }
+  void append_suffix(Token c) { suffix_.push_back(c); }
+  // Reverse the order of the suffix comments. When walking the tree in
+  // post-order we append suffix comments in reverse order, so this fixes them
+  // up.
+  void ReverseSuffix();
+
+  const std::vector<Token>& after() const { return after_; }
+  void append_after(Token c) { after_.push_back(c); }
+
+ private:
+  // Whole line comments before the expression.
+  std::vector<Token> before_;
+
+  // End-of-line comments after this expression.
+  std::vector<Token> suffix_;
+
+  // For top-level expressions only, after_ lists whole-line comments
+  // following the expression.
+  std::vector<Token> after_;
+
+  DISALLOW_COPY_AND_ASSIGN(Comments);
+};
+
+// ParseNode -------------------------------------------------------------------
+
+// A node in the AST.
+class ParseNode {
+ public:
+  ParseNode();
+  virtual ~ParseNode();
+
+  virtual const AccessorNode* AsAccessor() const;
+  virtual const BinaryOpNode* AsBinaryOp() const;
+  virtual const BlockCommentNode* AsBlockComment() const;
+  virtual const BlockNode* AsBlock() const;
+  virtual const ConditionNode* AsCondition() const;
+  virtual const EndNode* AsEnd() const;
+  virtual const FunctionCallNode* AsFunctionCall() const;
+  virtual const IdentifierNode* AsIdentifier() const;
+  virtual const ListNode* AsList() const;
+  virtual const LiteralNode* AsLiteral() const;
+  virtual const UnaryOpNode* AsUnaryOp() const;
+
+  virtual Value Execute(Scope* scope, Err* err) const = 0;
+
+  virtual LocationRange GetRange() const = 0;
+
+  // Returns an error with the given messages and the range set to something
+  // that indicates this node.
+  virtual Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const = 0;
+
+  // Generates a representation of this node in base::Value, to be used for
+  // exporting the tree as a JSON or formatted text with indents.
+  virtual base::Value GetJSONNode() const = 0;
+
+  const Comments* comments() const { return comments_.get(); }
+  Comments* comments_mutable();
+
+  static std::unique_ptr<ParseNode> BuildFromJSON(const base::Value& value);
+
+ protected:
+  // Helper functions for GetJSONNode. Creates and fills a Value object with
+  // given type (and value).
+  base::Value CreateJSONNode(const char* type, LocationRange location) const;
+  base::Value CreateJSONNode(const char* type,
+                             const std::string_view& value,
+                             LocationRange location) const;
+
+ private:
+  // Helper function for CreateJSONNode.
+  void AddCommentsJSONNodes(base::Value* out_value) const;
+
+  std::unique_ptr<Comments> comments_;
+
+  DISALLOW_COPY_AND_ASSIGN(ParseNode);
+};
+
+// AccessorNode ----------------------------------------------------------------
+
+// Access an array or scope element.
+//
+// Currently, such values are only read-only. In that you can do:
+//   a = obj1.a
+//   b = obj2[0]
+// But not
+//   obj1.a = 5
+//   obj2[0] = 6
+//
+// In the current design where the dot operator is used only for templates, we
+// explicitly don't want to allow you to do "invoker.foo = 5", so if we added
+// support for accessors to be lvalues, we would also need to add some concept
+// of a constant scope. Supporting this would also add a lot of complications
+// to the operator= implementation, since some accessors might return values
+// in the const root scope that shouldn't be modified. Without a strong
+// use-case for this, it seems simpler to just disallow it.
+//
+// Additionally, the left-hand-side of the accessor must currently be an
+// identifier. So you can't do things like:
+//   function_call()[1]
+//   a = b.c.d
+// These are easier to implement if we needed them but given the very limited
+// use cases for this, it hasn't seemed worth the bother.
+class AccessorNode : public ParseNode {
+ public:
+  AccessorNode();
+  ~AccessorNode() override;
+
+  const AccessorNode* AsAccessor() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<AccessorNode> NewFromJSON(const base::Value& value);
+
+  // Base is the thing on the left of the [] or dot, currently always required
+  // to be an identifier token.
+  const Token& base() const { return base_; }
+  void set_base(const Token& b) { base_ = b; }
+
+  // Subscript is the expression inside the []. Will be null if member is set.
+  const ParseNode* subscript() const { return subscript_.get(); }
+  void set_subscript(std::unique_ptr<ParseNode> key) {
+    subscript_ = std::move(key);
+  }
+
+  // The member is the identifier on the right hand side of the dot. Will be
+  // null if the index is set.
+  const IdentifierNode* member() const { return member_.get(); }
+  void set_member(std::unique_ptr<IdentifierNode> i) { member_ = std::move(i); }
+
+  void SetNewLocation(int line_number);
+
+  // Evaluates the index for list accessor operations and range checks it
+  // against the max length of the list. If the index is OK, sets
+  // |*computed_index| and returns true. Otherwise sets the |*err| and returns
+  // false.
+  bool ComputeAndValidateListIndex(Scope* scope,
+                                   size_t max_len,
+                                   size_t* computed_index,
+                                   Err* err) const;
+
+  static constexpr const char* kDumpNodeName = "ACCESSOR";
+
+ private:
+  Value ExecuteSubscriptAccess(Scope* scope, Err* err) const;
+  Value ExecuteArrayAccess(Scope* scope,
+                           const Value* base_value,
+                           Err* err) const;
+  Value ExecuteScopeSubscriptAccess(Scope* scope,
+                                    const Value* base_value,
+                                    Err* err) const;
+  Value ExecuteScopeAccess(Scope* scope, Err* err) const;
+
+  static constexpr const char* kDumpAccessorKind = "accessor_kind";
+  static constexpr const char* kDumpAccessorKindSubscript = "subscript";
+  static constexpr const char* kDumpAccessorKindMember = "member";
+
+  Token base_;
+
+  // Either index or member will be set according to what type of access this
+  // is.
+  std::unique_ptr<ParseNode> subscript_;
+  std::unique_ptr<IdentifierNode> member_;
+
+  DISALLOW_COPY_AND_ASSIGN(AccessorNode);
+};
+
+// BinaryOpNode ----------------------------------------------------------------
+
+class BinaryOpNode : public ParseNode {
+ public:
+  BinaryOpNode();
+  ~BinaryOpNode() override;
+
+  const BinaryOpNode* AsBinaryOp() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<BinaryOpNode> NewFromJSON(const base::Value& value);
+
+  const Token& op() const { return op_; }
+  void set_op(const Token& t) { op_ = t; }
+
+  const ParseNode* left() const { return left_.get(); }
+  void set_left(std::unique_ptr<ParseNode> left) { left_ = std::move(left); }
+
+  const ParseNode* right() const { return right_.get(); }
+  void set_right(std::unique_ptr<ParseNode> right) {
+    right_ = std::move(right);
+  }
+
+  static constexpr const char* kDumpNodeName = "BINARY";
+
+ private:
+  std::unique_ptr<ParseNode> left_;
+  Token op_;
+  std::unique_ptr<ParseNode> right_;
+
+  DISALLOW_COPY_AND_ASSIGN(BinaryOpNode);
+};
+
+// BlockNode -------------------------------------------------------------------
+
+class BlockNode : public ParseNode {
+ public:
+  // How Execute manages the scopes and results.
+  enum ResultMode {
+    // Creates a new scope for the execution of this block and returns it as
+    // a Value from Execute().
+    RETURNS_SCOPE,
+
+    // Executes in the context of the calling scope (variables set will go
+    // into the invoking scope) and Execute will return an empty Value.
+    DISCARDS_RESULT
+  };
+
+  BlockNode(ResultMode result_mode);
+  ~BlockNode() override;
+
+  const BlockNode* AsBlock() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<BlockNode> NewFromJSON(const base::Value& value);
+
+  void set_begin_token(const Token& t) { begin_token_ = t; }
+  void set_end(std::unique_ptr<EndNode> e) { end_ = std::move(e); }
+  const EndNode* End() const { return end_.get(); }
+
+  ResultMode result_mode() const { return result_mode_; }
+
+  const std::vector<std::unique_ptr<ParseNode>>& statements() const {
+    return statements_;
+  }
+  void append_statement(std::unique_ptr<ParseNode> s) {
+    statements_.push_back(std::move(s));
+  }
+
+  static constexpr const char* kDumpNodeName = "BLOCK";
+
+ private:
+  static constexpr const char* kDumpResultMode = "result_mode";
+  static constexpr const char* kDumpResultModeReturnsScope = "returns_scope";
+  static constexpr const char* kDumpResultModeDiscardsResult =
+      "discards_result";
+
+  const ResultMode result_mode_;
+
+  // Tokens corresponding to { and }, if any (may be NULL). The end is stored
+  // in a custom parse node so that it can have comments hung off of it.
+  Token begin_token_;
+  std::unique_ptr<EndNode> end_;
+
+  std::vector<std::unique_ptr<ParseNode>> statements_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlockNode);
+};
+
+// ConditionNode ---------------------------------------------------------------
+
+class ConditionNode : public ParseNode {
+ public:
+  ConditionNode();
+  ~ConditionNode() override;
+
+  const ConditionNode* AsCondition() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<ConditionNode> NewFromJSON(const base::Value& value);
+
+  void set_if_token(const Token& token) { if_token_ = token; }
+
+  const ParseNode* condition() const { return condition_.get(); }
+  void set_condition(std::unique_ptr<ParseNode> c) {
+    condition_ = std::move(c);
+  }
+
+  const BlockNode* if_true() const { return if_true_.get(); }
+  void set_if_true(std::unique_ptr<BlockNode> t) { if_true_ = std::move(t); }
+
+  // This is either empty, a block (for the else clause), or another
+  // condition.
+  const ParseNode* if_false() const { return if_false_.get(); }
+  void set_if_false(std::unique_ptr<ParseNode> f) { if_false_ = std::move(f); }
+
+  static constexpr const char* kDumpNodeName = "CONDITION";
+
+ private:
+  // Token corresponding to the "if" string.
+  Token if_token_;
+
+  std::unique_ptr<ParseNode> condition_;  // Always non-null.
+  std::unique_ptr<BlockNode> if_true_;    // Always non-null.
+  std::unique_ptr<ParseNode> if_false_;   // May be null.
+
+  DISALLOW_COPY_AND_ASSIGN(ConditionNode);
+};
+
+// FunctionCallNode ------------------------------------------------------------
+
+class FunctionCallNode : public ParseNode {
+ public:
+  FunctionCallNode();
+  ~FunctionCallNode() override;
+
+  const FunctionCallNode* AsFunctionCall() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<FunctionCallNode> NewFromJSON(
+      const base::Value& value);
+
+  const Token& function() const { return function_; }
+  void set_function(Token t) { function_ = t; }
+
+  const ListNode* args() const { return args_.get(); }
+  void set_args(std::unique_ptr<ListNode> a) { args_ = std::move(a); }
+
+  const BlockNode* block() const { return block_.get(); }
+  void set_block(std::unique_ptr<BlockNode> b) { block_ = std::move(b); }
+
+  void SetNewLocation(int line_number);
+
+  static constexpr const char* kDumpNodeName = "FUNCTION";
+
+ private:
+  Token function_;
+  std::unique_ptr<ListNode> args_;
+  std::unique_ptr<BlockNode> block_;  // May be null.
+
+  DISALLOW_COPY_AND_ASSIGN(FunctionCallNode);
+};
+
+// IdentifierNode --------------------------------------------------------------
+
+class IdentifierNode : public ParseNode {
+ public:
+  IdentifierNode();
+  explicit IdentifierNode(const Token& token);
+  ~IdentifierNode() override;
+
+  const IdentifierNode* AsIdentifier() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<IdentifierNode> NewFromJSON(const base::Value& value);
+
+  const Token& value() const { return value_; }
+  void set_value(const Token& t) { value_ = t; }
+
+  void SetNewLocation(int line_number);
+
+  static constexpr const char* kDumpNodeName = "IDENTIFIER";
+
+ private:
+  Token value_;
+
+  DISALLOW_COPY_AND_ASSIGN(IdentifierNode);
+};
+
+// ListNode --------------------------------------------------------------------
+
+class ListNode : public ParseNode {
+ public:
+  ListNode();
+  ~ListNode() override;
+
+  const ListNode* AsList() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<ListNode> NewFromJSON(const base::Value& value);
+
+  void set_begin_token(const Token& t) { begin_token_ = t; }
+  const Token& Begin() const { return begin_token_; }
+  void set_end(std::unique_ptr<EndNode> e) { end_ = std::move(e); }
+  const EndNode* End() const { return end_.get(); }
+
+  void append_item(std::unique_ptr<ParseNode> s) {
+    contents_.push_back(std::move(s));
+  }
+  const std::vector<std::unique_ptr<const ParseNode>>& contents() const {
+    return contents_;
+  }
+
+  void SortAsStringsList();
+  void SortAsTargetsList();
+
+  struct SortRange {
+    size_t begin;
+    size_t end;
+    SortRange(size_t begin, size_t end) : begin(begin), end(end) {}
+  };
+  // Only public for testing.
+  std::vector<SortRange> GetSortRanges() const;
+
+  static constexpr const char* kDumpNodeName = "LIST";
+
+ private:
+  template <typename Comparator>
+  void SortList(Comparator comparator);
+
+  // Tokens corresponding to the [ and ]. The end token is stored in inside an
+  // custom parse node so that it can have comments hung off of it.
+  Token begin_token_;
+  std::unique_ptr<EndNode> end_;
+
+  std::vector<std::unique_ptr<const ParseNode>> contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(ListNode);
+};
+
+// LiteralNode -----------------------------------------------------------------
+
+class LiteralNode : public ParseNode {
+ public:
+  LiteralNode();
+  explicit LiteralNode(const Token& token);
+  ~LiteralNode() override;
+
+  const LiteralNode* AsLiteral() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<LiteralNode> NewFromJSON(const base::Value& value);
+
+  const Token& value() const { return value_; }
+  void set_value(const Token& t) { value_ = t; }
+
+  void SetNewLocation(int line_number);
+
+  static constexpr const char* kDumpNodeName = "LITERAL";
+
+ private:
+  Token value_;
+
+  DISALLOW_COPY_AND_ASSIGN(LiteralNode);
+};
+
+// UnaryOpNode -----------------------------------------------------------------
+
+class UnaryOpNode : public ParseNode {
+ public:
+  UnaryOpNode();
+  ~UnaryOpNode() override;
+
+  const UnaryOpNode* AsUnaryOp() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<UnaryOpNode> NewFromJSON(const base::Value& value);
+
+  const Token& op() const { return op_; }
+  void set_op(const Token& t) { op_ = t; }
+
+  const ParseNode* operand() const { return operand_.get(); }
+  void set_operand(std::unique_ptr<ParseNode> operand) {
+    operand_ = std::move(operand);
+  }
+
+  static constexpr const char* kDumpNodeName = "UNARY";
+
+ private:
+  Token op_;
+  std::unique_ptr<ParseNode> operand_;
+
+  DISALLOW_COPY_AND_ASSIGN(UnaryOpNode);
+};
+
+// BlockCommentNode ------------------------------------------------------------
+
+// This node type is only used for standalone comments (that is, those not
+// specifically attached to another syntax element. The most common of these
+// is a standard header block. This node contains only the last line of such
+// a comment block as the anchor, and other lines of the block comment are
+// hung off of it as Before comments, similar to other syntax elements.
+class BlockCommentNode : public ParseNode {
+ public:
+  BlockCommentNode();
+  ~BlockCommentNode() override;
+
+  const BlockCommentNode* AsBlockComment() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<BlockCommentNode> NewFromJSON(
+      const base::Value& value);
+
+  const Token& comment() const { return comment_; }
+  void set_comment(const Token& t) { comment_ = t; }
+
+  static constexpr const char* kDumpNodeName = "BLOCK_COMMENT";
+
+ private:
+  Token comment_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlockCommentNode);
+};
+
+// EndNode ---------------------------------------------------------------------
+
+// This node type is used as the end_ object for lists and blocks (rather than
+// just the end ']', '}', or ')' token). This is so that during formatting
+// traversal there is a node that appears at the end of the block to which
+// comments can be attached.
+class EndNode : public ParseNode {
+ public:
+  explicit EndNode(const Token& token);
+  ~EndNode() override;
+
+  const EndNode* AsEnd() const override;
+  Value Execute(Scope* scope, Err* err) const override;
+  LocationRange GetRange() const override;
+  Err MakeErrorDescribing(
+      const std::string& msg,
+      const std::string& help = std::string()) const override;
+  base::Value GetJSONNode() const override;
+  static std::unique_ptr<EndNode> NewFromJSON(const base::Value& value);
+
+  const Token& value() const { return value_; }
+  void set_value(const Token& t) { value_ = t; }
+
+  static constexpr const char* kDumpNodeName = "END";
+
+ private:
+  Token value_;
+
+  DISALLOW_COPY_AND_ASSIGN(EndNode);
+};
+
+#endif  // TOOLS_GN_PARSE_TREE_H_
diff --git a/src/gn/parse_tree_unittest.cc b/src/gn/parse_tree_unittest.cc
new file mode 100644 (file)
index 0000000..ed28b54
--- /dev/null
@@ -0,0 +1,307 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/parse_tree.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "gn/input_file.h"
+#include "gn/scope.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(ParseTree, Accessor) {
+  TestWithScope setup;
+
+  // Make a pretend parse node with proper tracking that we can blame for the
+  // given value.
+  InputFile input_file(SourceFile("//foo"));
+  Token base_token(Location(&input_file, 1, 1, 1), Token::IDENTIFIER, "a");
+  Token member_token(Location(&input_file, 1, 1, 1), Token::IDENTIFIER, "b");
+
+  AccessorNode accessor;
+  accessor.set_base(base_token);
+
+  std::unique_ptr<IdentifierNode> member_identifier =
+      std::make_unique<IdentifierNode>(member_token);
+  accessor.set_member(std::move(member_identifier));
+
+  // The access should fail because a is not defined.
+  Err err;
+  Value result = accessor.Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+  EXPECT_EQ(Value::NONE, result.type());
+
+  // Define a as a Scope. It should still fail because b isn't defined.
+  err = Err();
+  setup.scope()->SetValue(
+      "a", Value(nullptr, std::make_unique<Scope>(setup.scope())), nullptr);
+  result = accessor.Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(Value::NONE, result.type());
+
+  // Define b, accessor should succeed now.
+  const int64_t kBValue = 42;
+  err = Err();
+  setup.scope()
+      ->GetMutableValue("a", Scope::SEARCH_NESTED, false)
+      ->scope_value()
+      ->SetValue("b", Value(nullptr, kBValue), nullptr);
+  result = accessor.Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(Value::INTEGER, result.type());
+  EXPECT_EQ(kBValue, result.int_value());
+}
+
+TEST(ParseTree, SubscriptedAccess) {
+  TestWithScope setup;
+  Err err;
+  TestParseInput values(
+      "list = [ 2, 3 ]\n"
+      "scope = {\n"
+      "  foo = 5\n"
+      "  bar = 8\n"
+      "}\n"
+      "bar_key = \"bar\""
+      "second_element_idx = 1");
+  values.parsed()->Execute(setup.scope(), &err);
+
+  EXPECT_FALSE(err.has_error());
+
+  Value first = setup.ExecuteExpression("list[0]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(first.type(), Value::INTEGER);
+  EXPECT_EQ(first.int_value(), 2);
+
+  Value second = setup.ExecuteExpression("list[second_element_idx]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(second.type(), Value::INTEGER);
+  EXPECT_EQ(second.int_value(), 3);
+
+  Value foo = setup.ExecuteExpression("scope[\"foo\"]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(foo.type(), Value::INTEGER);
+  EXPECT_EQ(foo.int_value(), 5);
+
+  Value bar = setup.ExecuteExpression("scope[bar_key]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(bar.type(), Value::INTEGER);
+  EXPECT_EQ(bar.int_value(), 8);
+
+  Value invalid1 = setup.ExecuteExpression("scope[second_element_idx]", &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+  EXPECT_EQ(invalid1.type(), Value::NONE);
+
+  Value invalid2 = setup.ExecuteExpression("list[bar_key]", &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+  EXPECT_EQ(invalid2.type(), Value::NONE);
+
+  Value invalid3 = setup.ExecuteExpression("scope[\"baz\"]", &err);
+  EXPECT_TRUE(err.has_error());
+  err = Err();
+  EXPECT_EQ(invalid3.type(), Value::NONE);
+}
+
+TEST(ParseTree, BlockUnusedVars) {
+  TestWithScope setup;
+
+  // Printing both values should be OK.
+  //
+  // The crazy template definition here is a way to execute a block without
+  // defining a target. Templates require that both the target_name and the
+  // invoker be used, which is what the assertion statement inside the template
+  // does.
+  TestParseInput input_all_used(
+      "template(\"foo\") { assert(target_name != 0 && invoker != 0) }\n"
+      "foo(\"a\") {\n"
+      "  a = 12\n"
+      "  b = 13\n"
+      "  print(\"$a $b\")\n"
+      "}");
+  EXPECT_FALSE(input_all_used.has_error());
+
+  Err err;
+  input_all_used.parsed()->Execute(setup.scope(), &err);
+  EXPECT_FALSE(err.has_error());
+
+  // Skipping one should throw an unused var error.
+  TestParseInput input_unused(
+      "foo(\"a\") {\n"
+      "  a = 12\n"
+      "  b = 13\n"
+      "  print(\"$a\")\n"
+      "}");
+  EXPECT_FALSE(input_unused.has_error());
+
+  input_unused.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+
+  // Also verify that the unused variable has the correct origin set. The
+  // origin will point to the value assigned to the variable (in this case, the
+  // "13" assigned to "b".
+  EXPECT_EQ(3, err.location().line_number());
+  EXPECT_EQ(7, err.location().column_number());
+}
+
+TEST(ParseTree, OriginForDereference) {
+  TestWithScope setup;
+  TestParseInput input(
+      "a = 6\n"
+      "get_target_outputs(a)");
+  EXPECT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+
+  // The origin for the "not a string" error message should be where the value
+  // was dereferenced (the "a" on the second line).
+  EXPECT_EQ(2, err.location().line_number());
+  EXPECT_EQ(20, err.location().column_number());
+}
+
+TEST(ParseTree, SortRangeExtraction) {
+  TestWithScope setup;
+
+  // Ranges are [begin, end).
+
+  {
+    TestParseInput input(
+        "sources = [\n"
+        "  \"a\",\n"
+        "  \"b\",\n"
+        "  \n"
+        "  #\n"
+        "  # Block\n"
+        "  #\n"
+        "  \n"
+        "  \"c\","
+        "  \"d\","
+        "]\n");
+    EXPECT_FALSE(input.has_error());
+    ASSERT_TRUE(input.parsed()->AsBlock());
+    ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp());
+    const BinaryOpNode* binop =
+        input.parsed()->AsBlock()->statements()[0]->AsBinaryOp();
+    ASSERT_TRUE(binop->right()->AsList());
+    const ListNode* list = binop->right()->AsList();
+    EXPECT_EQ(5u, list->contents().size());
+    auto ranges = list->GetSortRanges();
+    ASSERT_EQ(2u, ranges.size());
+    EXPECT_EQ(0u, ranges[0].begin);
+    EXPECT_EQ(2u, ranges[0].end);
+    EXPECT_EQ(3u, ranges[1].begin);
+    EXPECT_EQ(5u, ranges[1].end);
+  }
+
+  {
+    TestParseInput input(
+        "sources = [\n"
+        "  \"a\",\n"
+        "  \"b\",\n"
+        "  \n"
+        "  # Attached comment.\n"
+        "  \"c\","
+        "  \"d\","
+        "]\n");
+    EXPECT_FALSE(input.has_error());
+    ASSERT_TRUE(input.parsed()->AsBlock());
+    ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp());
+    const BinaryOpNode* binop =
+        input.parsed()->AsBlock()->statements()[0]->AsBinaryOp();
+    ASSERT_TRUE(binop->right()->AsList());
+    const ListNode* list = binop->right()->AsList();
+    EXPECT_EQ(4u, list->contents().size());
+    auto ranges = list->GetSortRanges();
+    ASSERT_EQ(2u, ranges.size());
+    EXPECT_EQ(0u, ranges[0].begin);
+    EXPECT_EQ(2u, ranges[0].end);
+    EXPECT_EQ(2u, ranges[1].begin);
+    EXPECT_EQ(4u, ranges[1].end);
+  }
+
+  {
+    TestParseInput input(
+        "sources = [\n"
+        "  # At end of list.\n"
+        "  \"zzzzzzzzzzz.cc\","
+        "]\n");
+    EXPECT_FALSE(input.has_error());
+    ASSERT_TRUE(input.parsed()->AsBlock());
+    ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp());
+    const BinaryOpNode* binop =
+        input.parsed()->AsBlock()->statements()[0]->AsBinaryOp();
+    ASSERT_TRUE(binop->right()->AsList());
+    const ListNode* list = binop->right()->AsList();
+    EXPECT_EQ(1u, list->contents().size());
+    auto ranges = list->GetSortRanges();
+    ASSERT_EQ(1u, ranges.size());
+    EXPECT_EQ(0u, ranges[0].begin);
+    EXPECT_EQ(1u, ranges[0].end);
+  }
+
+  {
+    TestParseInput input(
+        "sources = [\n"
+        "  # Block at start.\n"
+        "  \n"
+        "  \"z.cc\","
+        "  \"y.cc\","
+        "]\n");
+    EXPECT_FALSE(input.has_error());
+    ASSERT_TRUE(input.parsed()->AsBlock());
+    ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp());
+    const BinaryOpNode* binop =
+        input.parsed()->AsBlock()->statements()[0]->AsBinaryOp();
+    ASSERT_TRUE(binop->right()->AsList());
+    const ListNode* list = binop->right()->AsList();
+    EXPECT_EQ(3u, list->contents().size());
+    auto ranges = list->GetSortRanges();
+    ASSERT_EQ(1u, ranges.size());
+    EXPECT_EQ(1u, ranges[0].begin);
+    EXPECT_EQ(3u, ranges[0].end);
+  }
+}
+
+TEST(ParseTree, Integers) {
+  static const char* const kGood[] = {
+      "0",
+      "10",
+      "-54321",
+      "9223372036854775807",   // INT64_MAX
+      "-9223372036854775808",  // INT64_MIN
+  };
+  for (auto* s : kGood) {
+    TestParseInput input(std::string("x = ") + s);
+    EXPECT_FALSE(input.has_error());
+
+    TestWithScope setup;
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    EXPECT_FALSE(err.has_error());
+  }
+
+  static const char* const kBad[] = {
+      "-0",
+      "010",
+      "-010",
+      "9223372036854775808",   // INT64_MAX + 1
+      "-9223372036854775809",  // INT64_MIN - 1
+  };
+  for (auto* s : kBad) {
+    TestParseInput input(std::string("x = ") + s);
+    EXPECT_FALSE(input.has_error());
+
+    TestWithScope setup;
+    Err err;
+    input.parsed()->Execute(setup.scope(), &err);
+    EXPECT_TRUE(err.has_error());
+  }
+}
diff --git a/src/gn/parser.cc b/src/gn/parser.cc
new file mode 100644 (file)
index 0000000..07e9ba3
--- /dev/null
@@ -0,0 +1,941 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/parser.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "gn/functions.h"
+#include "gn/operators.h"
+#include "gn/token.h"
+
+const char kGrammar_Help[] =
+    R"*(Language and grammar for GN build files
+
+Tokens
+
+  GN build files are read as sequences of tokens.  While splitting the file
+  into tokens, the next token is the longest sequence of characters that form a
+  valid token.
+
+White space and comments
+
+  White space is comprised of spaces (U+0020), horizontal tabs (U+0009),
+  carriage returns (U+000D), and newlines (U+000A).
+
+  Comments start at the character "#" and stop at the next newline.
+
+  White space and comments are ignored except that they may separate tokens
+  that would otherwise combine into a single token.
+
+Identifiers
+
+  Identifiers name variables and functions.
+
+      identifier = letter { letter | digit } .
+      letter     = "A" ... "Z" | "a" ... "z" | "_" .
+      digit      = "0" ... "9" .
+
+Keywords
+
+  The following keywords are reserved and may not be used as identifiers:
+
+          else    false   if      true
+
+Integer literals
+
+  An integer literal represents a decimal integer value.
+
+      integer = [ "-" ] digit { digit } .
+
+  Leading zeros and negative zero are disallowed.
+
+String literals
+
+  A string literal represents a string value consisting of the quoted
+  characters with possible escape sequences and variable expansions.
+
+      string           = `"` { char | escape | expansion } `"` .
+      escape           = `\` ( "$" | `"` | char ) .
+      BracketExpansion = "{" ( identifier | ArrayAccess | ScopeAccess ) "}" .
+      Hex              = "0x" [0-9A-Fa-f][0-9A-Fa-f]
+      expansion        = "$" ( identifier | BracketExpansion | Hex ) .
+      char             = /* any character except "$", `"`, or newline */ .
+
+  After a backslash, certain sequences represent special characters:
+
+          \"    U+0022    quotation mark
+          \$    U+0024    dollar sign
+          \\    U+005C    backslash
+
+  All other backslashes represent themselves.
+
+  To insert an arbitrary byte value, use $0xFF. For example, to insert a
+  newline character: "Line one$0x0ALine two".
+
+  An expansion will evaluate the variable following the '$' and insert a
+  stringified version of it into the result. For example, to concat two path
+  components with a slash separating them:
+    "$var_one/$var_two"
+  Use the "${var_one}" format to be explicitly deliniate the variable for
+  otherwise-ambiguous cases.
+
+Punctuation
+
+  The following character sequences represent punctuation:
+
+          +       +=      ==      !=      (       )
+          -       -=      <       <=      [       ]
+          !       =       >       >=      {       }
+                          &&      ||      .       ,
+
+Grammar
+
+  The input tokens form a syntax tree following a context-free grammar:
+
+      File = StatementList .
+
+      Statement     = Assignment | Call | Condition .
+      LValue        = identifier | ArrayAccess | ScopeAccess .
+      Assignment    = LValue AssignOp Expr .
+      Call          = identifier "(" [ ExprList ] ")" [ Block ] .
+      Condition     = "if" "(" Expr ")" Block
+                      [ "else" ( Condition | Block ) ] .
+      Block         = "{" StatementList "}" .
+      StatementList = { Statement } .
+
+      ArrayAccess = identifier "[" Expr "]" .
+      ScopeAccess = identifier "." identifier .
+      Expr        = UnaryExpr | Expr BinaryOp Expr .
+      UnaryExpr   = PrimaryExpr | UnaryOp UnaryExpr .
+      PrimaryExpr = identifier | integer | string | Call
+                  | ArrayAccess | ScopeAccess | Block
+                  | "(" Expr ")"
+                  | "[" [ ExprList [ "," ] ] "]" .
+      ExprList    = Expr { "," Expr } .
+
+      AssignOp = "=" | "+=" | "-=" .
+      UnaryOp  = "!" .
+      BinaryOp = "+" | "-"                  // highest priority
+               | "<" | "<=" | ">" | ">="
+               | "==" | "!="
+               | "&&"
+               | "||" .                     // lowest priority
+
+  All binary operators are left-associative.
+
+Types
+
+  The GN language is dynamically typed. The following types are used:
+
+   - Boolean: Uses the keywords "true" and "false". There is no implicit
+     conversion between booleans and integers.
+
+   - Integers: All numbers in GN are signed 64-bit integers.
+
+   - Strings: Strings are 8-bit with no enforced encoding. When a string is
+     used to interact with other systems with particular encodings (like the
+     Windows and Mac filesystems) it is assumed to be UTF-8. See "String
+     literals" above for more.
+
+   - Lists: Lists are arbitrary-length ordered lists of values. See "Lists"
+     below for more.
+
+   - Scopes: Scopes are like dictionaries that use variable names for keys. See
+     "Scopes" below for more.
+
+Lists
+
+  Lists are created with [] and using commas to separate items:
+
+       mylist = [ 0, 1, 2, "some string" ]
+
+  A comma after the last item is optional. Lists are dereferenced using 0-based
+  indexing:
+
+       mylist[0] += 1
+       var = mylist[2]
+
+  Lists can be concatenated using the '+' and '+=' operators. Bare values can
+  not be concatenated with lists, to add a single item, it must be put into a
+  list of length one.
+
+  Items can be removed from lists using the '-' and '-=' operators. This will
+  remove all occurrences of every item in the right-hand list from the
+  left-hand list. It is an error to remove an item not in the list. This is to
+  prevent common typos and to detect dead code that is removing things that no
+  longer apply.
+
+  It is an error to use '=' to replace a nonempty list with another nonempty
+  list. This is to prevent accidentally overwriting data when in most cases
+  '+=' was intended. To overwrite a list on purpose, first assign it to the
+  empty list:
+
+    mylist = []
+    mylist = otherlist
+
+Scopes
+
+  All execution happens in the context of a scope which holds the current state
+  (like variables). With the exception of loops and conditions, '{' introduces
+  a new scope that has a parent reference to the old scope.
+
+  Variable reads recursively search all nested scopes until the variable is
+  found or there are no more scopes. Variable writes always go into the current
+  scope. This means that after the closing '}' (again excepting loops and
+  conditions), all local variables will be restored to the previous values.
+  This also means that "foo = foo" can do useful work by copying a variable
+  into the current scope that was defined in a containing scope.
+
+  Scopes can also be assigned to variables. Such scopes can be created by
+  functions like exec_script, when invoking a template (the template code
+  refers to the variables set by the invoking code by the implicitly-created
+  "invoker" scope), or explicitly like:
+
+    empty_scope = {}
+    myvalues = {
+      foo = 21
+      bar = "something"
+    }
+
+  Inside such a scope definition can be any GN code including conditionals and
+  function calls. After the close of the scope, it will contain all variables
+  explicitly set by the code contained inside it. After this, the values can be
+  read, modified, or added to:
+
+    myvalues.foo += 2
+    empty_scope.new_thing = [ 1, 2, 3 ]
+
+  Scope equality is defined as single-level scopes identical within the current
+  scope. That is, all values in the first scope must be present and identical
+  within the second, and vice versa. Note that this means inherited scopes are
+  always unequal by definition.
+)*";
+
+// Precedence constants.
+//
+// Currently all operators are left-associative so this list is sequential. To
+// implement a right-assocative operators in a Pratt parser we would leave gaps
+// in between the constants, and right-associative operators get a precedence
+// of "<left-associated-precedence> - 1".
+enum Precedence {
+  PRECEDENCE_ASSIGNMENT = 1,  // Lowest precedence.
+  PRECEDENCE_OR = 2,
+  PRECEDENCE_AND = 3,
+  PRECEDENCE_EQUALITY = 4,
+  PRECEDENCE_RELATION = 5,
+  PRECEDENCE_SUM = 6,
+  PRECEDENCE_PREFIX = 7,
+  PRECEDENCE_CALL = 8,
+  PRECEDENCE_DOT = 9,  // Highest precedence.
+};
+
+// The top-level for blocks/ifs is recursive descent, the expression parser is
+// a Pratt parser. The basic idea there is to have the precedences (and
+// associativities) encoded relative to each other and only parse up until you
+// hit something of that precedence. There's a dispatch table in expressions_
+// at the top of parser.cc that describes how each token dispatches if it's
+// seen as either a prefix or infix operator, and if it's infix, what its
+// precedence is.
+//
+// References:
+// http://javascript.crockford.com/tdop/tdop.html
+// http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
+
+// Indexed by Token::Type.
+ParserHelper Parser::expressions_[] = {
+    {nullptr, nullptr, -1},                                   // INVALID
+    {&Parser::Literal, nullptr, -1},                          // INTEGER
+    {&Parser::Literal, nullptr, -1},                          // STRING
+    {&Parser::Literal, nullptr, -1},                          // TRUE_TOKEN
+    {&Parser::Literal, nullptr, -1},                          // FALSE_TOKEN
+    {nullptr, &Parser::Assignment, PRECEDENCE_ASSIGNMENT},    // EQUAL
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_SUM},       // PLUS
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_SUM},       // MINUS
+    {nullptr, &Parser::Assignment, PRECEDENCE_ASSIGNMENT},    // PLUS_EQUALS
+    {nullptr, &Parser::Assignment, PRECEDENCE_ASSIGNMENT},    // MINUS_EQUALS
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_EQUALITY},  // EQUAL_EQUAL
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_EQUALITY},  // NOT_EQUAL
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_RELATION},  // LESS_EQUAL
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_RELATION},  // GREATER_EQUAL
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_RELATION},  // LESS_THAN
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_RELATION},  // GREATER_THAN
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_AND},       // BOOLEAN_AND
+    {nullptr, &Parser::BinaryOperator, PRECEDENCE_OR},        // BOOLEAN_OR
+    {&Parser::Not, nullptr, -1},                              // BANG
+    {nullptr, &Parser::DotOperator, PRECEDENCE_DOT},          // DOT
+    {&Parser::Group, nullptr, -1},                            // LEFT_PAREN
+    {nullptr, nullptr, -1},                                   // RIGHT_PAREN
+    {&Parser::List, &Parser::Subscript, PRECEDENCE_CALL},     // LEFT_BRACKET
+    {nullptr, nullptr, -1},                                   // RIGHT_BRACKET
+    {&Parser::Block, nullptr, -1},                            // LEFT_BRACE
+    {nullptr, nullptr, -1},                                   // RIGHT_BRACE
+    {nullptr, nullptr, -1},                                   // IF
+    {nullptr, nullptr, -1},                                   // ELSE
+    {&Parser::Name, &Parser::IdentifierOrCall, PRECEDENCE_CALL},  // IDENTIFIER
+    {nullptr, nullptr, -1},                                       // COMMA
+    {nullptr, nullptr, -1},                // UNCLASSIFIED_COMMENT
+    {nullptr, nullptr, -1},                // LINE_COMMENT
+    {nullptr, nullptr, -1},                // SUFFIX_COMMENT
+    {&Parser::BlockComment, nullptr, -1},  // BLOCK_COMMENT
+};
+
+Parser::Parser(const std::vector<Token>& tokens, Err* err)
+    : invalid_token_(Location(), Token::INVALID, std::string_view()),
+      err_(err),
+      cur_(0) {
+  for (const auto& token : tokens) {
+    switch (token.type()) {
+      case Token::LINE_COMMENT:
+        line_comment_tokens_.push_back(token);
+        break;
+      case Token::SUFFIX_COMMENT:
+        suffix_comment_tokens_.push_back(token);
+        break;
+      default:
+        // Note that BLOCK_COMMENTs (top-level standalone comments) are passed
+        // through the real parser.
+        tokens_.push_back(token);
+        break;
+    }
+  }
+}
+
+Parser::~Parser() = default;
+
+// static
+std::unique_ptr<ParseNode> Parser::Parse(const std::vector<Token>& tokens,
+                                         Err* err) {
+  Parser p(tokens, err);
+  return p.ParseFile();
+}
+
+// static
+std::unique_ptr<ParseNode> Parser::ParseExpression(
+    const std::vector<Token>& tokens,
+    Err* err) {
+  Parser p(tokens, err);
+  std::unique_ptr<ParseNode> expr = p.ParseExpression();
+  if (!p.at_end() && !err->has_error()) {
+    *err = Err(p.cur_token(), "Trailing garbage");
+    return nullptr;
+  }
+  return expr;
+}
+
+// static
+std::unique_ptr<ParseNode> Parser::ParseValue(const std::vector<Token>& tokens,
+                                              Err* err) {
+  for (const Token& token : tokens) {
+    switch (token.type()) {
+      case Token::INTEGER:
+      case Token::STRING:
+      case Token::TRUE_TOKEN:
+      case Token::FALSE_TOKEN:
+      case Token::LEFT_BRACKET:
+      case Token::RIGHT_BRACKET:
+      case Token::COMMA:
+        continue;
+      default:
+        *err = Err(token, "Invalid token in literal value");
+        return nullptr;
+    }
+  }
+
+  return ParseExpression(tokens, err);
+}
+
+bool Parser::IsAssignment(const ParseNode* node) const {
+  return node && node->AsBinaryOp() &&
+         (node->AsBinaryOp()->op().type() == Token::EQUAL ||
+          node->AsBinaryOp()->op().type() == Token::PLUS_EQUALS ||
+          node->AsBinaryOp()->op().type() == Token::MINUS_EQUALS);
+}
+
+bool Parser::IsStatementBreak(Token::Type token_type) const {
+  switch (token_type) {
+    case Token::IDENTIFIER:
+    case Token::LEFT_BRACE:
+    case Token::RIGHT_BRACE:
+    case Token::IF:
+    case Token::ELSE:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool Parser::LookAhead(Token::Type type) {
+  if (at_end())
+    return false;
+  return cur_token().type() == type;
+}
+
+bool Parser::Match(Token::Type type) {
+  if (!LookAhead(type))
+    return false;
+  Consume();
+  return true;
+}
+
+const Token& Parser::Consume(Token::Type type, const char* error_message) {
+  Token::Type types[1] = {type};
+  return Consume(types, 1, error_message);
+}
+
+const Token& Parser::Consume(Token::Type* types,
+                             size_t num_types,
+                             const char* error_message) {
+  if (has_error()) {
+    // Don't overwrite current error, but make progress through tokens so that
+    // a loop that's expecting a particular token will still terminate.
+    if (!at_end())
+      cur_++;
+    return invalid_token_;
+  }
+  if (at_end()) {
+    const char kEOFMsg[] = "I hit EOF instead.";
+    if (tokens_.empty())
+      *err_ = Err(Location(), error_message, kEOFMsg);
+    else
+      *err_ = Err(tokens_[tokens_.size() - 1], error_message, kEOFMsg);
+    return invalid_token_;
+  }
+
+  for (size_t i = 0; i < num_types; ++i) {
+    if (cur_token().type() == types[i])
+      return Consume();
+  }
+  *err_ = Err(cur_token(), error_message);
+  return invalid_token_;
+}
+
+const Token& Parser::Consume() {
+  return tokens_[cur_++];
+}
+
+std::unique_ptr<ParseNode> Parser::ParseExpression() {
+  return ParseExpression(0);
+}
+
+std::unique_ptr<ParseNode> Parser::ParseExpression(int precedence) {
+  if (at_end())
+    return std::unique_ptr<ParseNode>();
+
+  const Token& token = Consume();
+  PrefixFunc prefix = expressions_[token.type()].prefix;
+
+  if (prefix == nullptr) {
+    *err_ = Err(token, "Unexpected token '" + std::string(token.value()) + "'");
+    return std::unique_ptr<ParseNode>();
+  }
+
+  std::unique_ptr<ParseNode> left = (this->*prefix)(token);
+  if (has_error())
+    return left;
+
+  while (!at_end() && !IsStatementBreak(cur_token().type()) &&
+         precedence <= expressions_[cur_token().type()].precedence) {
+    const Token& next_token = Consume();
+    InfixFunc infix = expressions_[next_token.type()].infix;
+    if (infix == nullptr) {
+      *err_ = Err(next_token,
+                  "Unexpected token '" + std::string(next_token.value()) + "'");
+      return std::unique_ptr<ParseNode>();
+    }
+    left = (this->*infix)(std::move(left), next_token);
+    if (has_error())
+      return std::unique_ptr<ParseNode>();
+  }
+
+  return left;
+}
+
+std::unique_ptr<ParseNode> Parser::Block(const Token& token) {
+  // This entrypoint into ParseBlock means it's part of an expression and we
+  // always want the result.
+  return ParseBlock(token, BlockNode::RETURNS_SCOPE);
+}
+
+std::unique_ptr<ParseNode> Parser::Literal(const Token& token) {
+  return std::make_unique<LiteralNode>(token);
+}
+
+std::unique_ptr<ParseNode> Parser::Name(const Token& token) {
+  return IdentifierOrCall(std::unique_ptr<ParseNode>(), token);
+}
+
+std::unique_ptr<ParseNode> Parser::BlockComment(const Token& token) {
+  std::unique_ptr<BlockCommentNode> comment =
+      std::make_unique<BlockCommentNode>();
+  comment->set_comment(token);
+  return std::move(comment);
+}
+
+std::unique_ptr<ParseNode> Parser::Group(const Token& token) {
+  std::unique_ptr<ParseNode> expr = ParseExpression();
+  if (has_error())
+    return std::unique_ptr<ParseNode>();
+  Consume(Token::RIGHT_PAREN, "Expected ')'");
+  return expr;
+}
+
+std::unique_ptr<ParseNode> Parser::Not(const Token& token) {
+  std::unique_ptr<ParseNode> expr = ParseExpression(PRECEDENCE_PREFIX + 1);
+  if (has_error())
+    return std::unique_ptr<ParseNode>();
+  if (!expr) {
+    if (!has_error())
+      *err_ = Err(token, "Expected right-hand side for '!'.");
+    return std::unique_ptr<ParseNode>();
+  }
+  std::unique_ptr<UnaryOpNode> unary_op = std::make_unique<UnaryOpNode>();
+  unary_op->set_op(token);
+  unary_op->set_operand(std::move(expr));
+  return std::move(unary_op);
+}
+
+std::unique_ptr<ParseNode> Parser::List(const Token& node) {
+  std::unique_ptr<ParseNode> list(ParseList(node, Token::RIGHT_BRACKET, true));
+  if (!has_error() && !at_end())
+    Consume(Token::RIGHT_BRACKET, "Expected ']'");
+  return list;
+}
+
+std::unique_ptr<ParseNode> Parser::BinaryOperator(
+    std::unique_ptr<ParseNode> left,
+    const Token& token) {
+  std::unique_ptr<ParseNode> right =
+      ParseExpression(expressions_[token.type()].precedence + 1);
+  if (!right) {
+    if (!has_error()) {
+      *err_ = Err(token, "Expected right-hand side for '" +
+                             std::string(token.value()) + "'");
+    }
+    return std::unique_ptr<ParseNode>();
+  }
+  std::unique_ptr<BinaryOpNode> binary_op = std::make_unique<BinaryOpNode>();
+  binary_op->set_op(token);
+  binary_op->set_left(std::move(left));
+  binary_op->set_right(std::move(right));
+  return std::move(binary_op);
+}
+
+std::unique_ptr<ParseNode> Parser::IdentifierOrCall(
+    std::unique_ptr<ParseNode> left,
+    const Token& token) {
+  std::unique_ptr<ListNode> list = std::make_unique<ListNode>();
+  list->set_begin_token(token);
+  list->set_end(std::make_unique<EndNode>(token));
+  std::unique_ptr<BlockNode> block;
+  bool has_arg = false;
+  if (LookAhead(Token::LEFT_PAREN)) {
+    const Token& start_token = Consume();
+    // Parsing a function call.
+    has_arg = true;
+    if (Match(Token::RIGHT_PAREN)) {
+      // Nothing, just an empty call.
+    } else {
+      list = ParseList(start_token, Token::RIGHT_PAREN, false);
+      if (has_error())
+        return std::unique_ptr<ParseNode>();
+      Consume(Token::RIGHT_PAREN, "Expected ')' after call");
+    }
+    // Optionally with a scope.
+    if (LookAhead(Token::LEFT_BRACE)) {
+      block = ParseBlock(Consume(), BlockNode::DISCARDS_RESULT);
+      if (has_error())
+        return std::unique_ptr<ParseNode>();
+    }
+  }
+
+  if (!left && !has_arg) {
+    // Not a function call, just a standalone identifier.
+    return std::make_unique<IdentifierNode>(token);
+  }
+  std::unique_ptr<FunctionCallNode> func_call =
+      std::make_unique<FunctionCallNode>();
+  func_call->set_function(token);
+  func_call->set_args(std::move(list));
+  if (block)
+    func_call->set_block(std::move(block));
+  return std::move(func_call);
+}
+
+std::unique_ptr<ParseNode> Parser::Assignment(std::unique_ptr<ParseNode> left,
+                                              const Token& token) {
+  if (left->AsIdentifier() == nullptr && left->AsAccessor() == nullptr) {
+    *err_ = Err(left.get(),
+                "The left-hand side of an assignment must be an identifier, "
+                "scope access, or array access.");
+    return std::unique_ptr<ParseNode>();
+  }
+  std::unique_ptr<ParseNode> value = ParseExpression(PRECEDENCE_ASSIGNMENT);
+  if (!value) {
+    if (!has_error())
+      *err_ = Err(token, "Expected right-hand side for assignment.");
+    return std::unique_ptr<ParseNode>();
+  }
+  std::unique_ptr<BinaryOpNode> assign = std::make_unique<BinaryOpNode>();
+  assign->set_op(token);
+  assign->set_left(std::move(left));
+  assign->set_right(std::move(value));
+  return std::move(assign);
+}
+
+std::unique_ptr<ParseNode> Parser::Subscript(std::unique_ptr<ParseNode> left,
+                                             const Token& token) {
+  // TODO: Maybe support more complex expressions like a[0][0]. This would
+  // require work on the evaluator too.
+  if (left->AsIdentifier() == nullptr) {
+    *err_ = Err(
+        left.get(), "May only subscript identifiers.",
+        "The thing on the left hand side of the [] must be an identifier\n"
+        "and not an expression. If you need this, you'll have to assign the\n"
+        "value to a temporary before subscripting. Sorry.");
+    return std::unique_ptr<ParseNode>();
+  }
+  std::unique_ptr<ParseNode> value = ParseExpression();
+  Consume(Token::RIGHT_BRACKET, "Expecting ']' after subscript.");
+  std::unique_ptr<AccessorNode> accessor = std::make_unique<AccessorNode>();
+  accessor->set_base(left->AsIdentifier()->value());
+  accessor->set_subscript(std::move(value));
+  return std::move(accessor);
+}
+
+std::unique_ptr<ParseNode> Parser::DotOperator(std::unique_ptr<ParseNode> left,
+                                               const Token& token) {
+  if (left->AsIdentifier() == nullptr) {
+    *err_ = Err(
+        left.get(), "May only use \".\" for identifiers.",
+        "The thing on the left hand side of the dot must be an identifier\n"
+        "and not an expression. If you need this, you'll have to assign the\n"
+        "value to a temporary first. Sorry.");
+    return std::unique_ptr<ParseNode>();
+  }
+
+  std::unique_ptr<ParseNode> right = ParseExpression(PRECEDENCE_DOT);
+  if (!right || !right->AsIdentifier()) {
+    *err_ = Err(
+        token, "Expected identifier for right-hand-side of \".\"",
+        "Good: a.cookies\nBad: a.42\nLooks good but still bad: a.cookies()");
+    return std::unique_ptr<ParseNode>();
+  }
+
+  std::unique_ptr<AccessorNode> accessor = std::make_unique<AccessorNode>();
+  accessor->set_base(left->AsIdentifier()->value());
+  accessor->set_member(std::unique_ptr<IdentifierNode>(
+      static_cast<IdentifierNode*>(right.release())));
+  return std::move(accessor);
+}
+
+// Does not Consume the start or end token.
+std::unique_ptr<ListNode> Parser::ParseList(const Token& start_token,
+                                            Token::Type stop_before,
+                                            bool allow_trailing_comma) {
+  std::unique_ptr<ListNode> list = std::make_unique<ListNode>();
+  list->set_begin_token(start_token);
+  bool just_got_comma = false;
+  bool first_time = true;
+  while (!LookAhead(stop_before)) {
+    if (!first_time) {
+      if (!just_got_comma) {
+        // Require commas separate things in lists.
+        *err_ = Err(cur_token(), "Expected comma between items.");
+        return std::unique_ptr<ListNode>();
+      }
+    }
+    first_time = false;
+
+    // Why _OR? We're parsing things that are higher precedence than the ,
+    // that separates the items of the list. , should appear lower than
+    // boolean expressions (the lowest of which is OR), but above assignments.
+    list->append_item(ParseExpression(PRECEDENCE_OR));
+    if (has_error())
+      return std::unique_ptr<ListNode>();
+    if (at_end()) {
+      *err_ =
+          Err(tokens_[tokens_.size() - 1], "Unexpected end of file in list.");
+      return std::unique_ptr<ListNode>();
+    }
+    if (list->contents().back()->AsBlockComment()) {
+      // If there was a comment inside the list, we don't need a comma to the
+      // next item, so pretend we got one, if we're expecting one.
+      just_got_comma = allow_trailing_comma;
+    } else {
+      just_got_comma = Match(Token::COMMA);
+    }
+  }
+  if (just_got_comma && !allow_trailing_comma) {
+    *err_ = Err(cur_token(), "Trailing comma");
+    return std::unique_ptr<ListNode>();
+  }
+  list->set_end(std::make_unique<EndNode>(cur_token()));
+  return list;
+}
+
+std::unique_ptr<ParseNode> Parser::ParseFile() {
+  std::unique_ptr<BlockNode> file =
+      std::make_unique<BlockNode>(BlockNode::DISCARDS_RESULT);
+  for (;;) {
+    if (at_end())
+      break;
+    std::unique_ptr<ParseNode> statement = ParseStatement();
+    if (!statement)
+      break;
+    file->append_statement(std::move(statement));
+  }
+  if (!at_end() && !has_error())
+    *err_ = Err(cur_token(), "Unexpected here, should be newline.");
+  if (has_error())
+    return std::unique_ptr<ParseNode>();
+
+  // TODO(scottmg): If this is measurably expensive, it could be done only
+  // when necessary (when reformatting, or during tests). Comments are
+  // separate from the parse tree at this point, so downstream code can remain
+  // ignorant of them.
+  AssignComments(file.get());
+
+  return std::move(file);
+}
+
+std::unique_ptr<ParseNode> Parser::ParseStatement() {
+  if (LookAhead(Token::IF)) {
+    return ParseCondition();
+  } else if (LookAhead(Token::BLOCK_COMMENT)) {
+    return BlockComment(Consume());
+  } else {
+    // TODO(scottmg): Is this too strict? Just drop all the testing if we want
+    // to allow "pointless" expressions and return ParseExpression() directly.
+    std::unique_ptr<ParseNode> stmt = ParseExpression();
+    if (stmt) {
+      if (stmt->AsFunctionCall() || IsAssignment(stmt.get()))
+        return stmt;
+    }
+    if (!has_error()) {
+      const Token& token = cur_or_last_token();
+      *err_ = Err(token, "Expecting assignment or function call.");
+    }
+    return std::unique_ptr<ParseNode>();
+  }
+}
+
+std::unique_ptr<BlockNode> Parser::ParseBlock(
+    const Token& begin_brace,
+    BlockNode::ResultMode result_mode) {
+  if (has_error())
+    return std::unique_ptr<BlockNode>();
+  std::unique_ptr<BlockNode> block = std::make_unique<BlockNode>(result_mode);
+  block->set_begin_token(begin_brace);
+
+  for (;;) {
+    if (LookAhead(Token::RIGHT_BRACE)) {
+      block->set_end(std::make_unique<EndNode>(Consume()));
+      break;
+    }
+
+    std::unique_ptr<ParseNode> statement = ParseStatement();
+    if (!statement)
+      return std::unique_ptr<BlockNode>();
+    block->append_statement(std::move(statement));
+  }
+  return block;
+}
+
+std::unique_ptr<ParseNode> Parser::ParseCondition() {
+  std::unique_ptr<ConditionNode> condition = std::make_unique<ConditionNode>();
+  condition->set_if_token(Consume(Token::IF, "Expected 'if'"));
+  Consume(Token::LEFT_PAREN, "Expected '(' after 'if'.");
+  condition->set_condition(ParseExpression());
+  if (IsAssignment(condition->condition()))
+    *err_ = Err(condition->condition(), "Assignment not allowed in 'if'.");
+  Consume(Token::RIGHT_PAREN, "Expected ')' after condition of 'if'.");
+  condition->set_if_true(ParseBlock(
+      Consume(Token::LEFT_BRACE, "Expected '{' to start 'if' block."),
+      BlockNode::DISCARDS_RESULT));
+  if (Match(Token::ELSE)) {
+    if (LookAhead(Token::LEFT_BRACE)) {
+      condition->set_if_false(
+          ParseBlock(Consume(), BlockNode::DISCARDS_RESULT));
+    } else if (LookAhead(Token::IF)) {
+      condition->set_if_false(ParseStatement());
+    } else {
+      *err_ = Err(cur_or_last_token(), "Expected '{' or 'if' after 'else'.");
+      return std::unique_ptr<ParseNode>();
+    }
+  }
+  if (has_error())
+    return std::unique_ptr<ParseNode>();
+  return std::move(condition);
+}
+
+void Parser::TraverseOrder(const ParseNode* root,
+                           std::vector<const ParseNode*>* pre,
+                           std::vector<const ParseNode*>* post) {
+  if (root) {
+    pre->push_back(root);
+
+    if (const AccessorNode* accessor = root->AsAccessor()) {
+      TraverseOrder(accessor->subscript(), pre, post);
+      TraverseOrder(accessor->member(), pre, post);
+    } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
+      TraverseOrder(binop->left(), pre, post);
+      TraverseOrder(binop->right(), pre, post);
+    } else if (const BlockNode* block = root->AsBlock()) {
+      for (const auto& statement : block->statements())
+        TraverseOrder(statement.get(), pre, post);
+      TraverseOrder(block->End(), pre, post);
+    } else if (const ConditionNode* condition = root->AsCondition()) {
+      TraverseOrder(condition->condition(), pre, post);
+      TraverseOrder(condition->if_true(), pre, post);
+      TraverseOrder(condition->if_false(), pre, post);
+    } else if (const FunctionCallNode* func_call = root->AsFunctionCall()) {
+      TraverseOrder(func_call->args(), pre, post);
+      TraverseOrder(func_call->block(), pre, post);
+    } else if (root->AsIdentifier()) {
+      // Nothing.
+    } else if (const ListNode* list = root->AsList()) {
+      for (const auto& node : list->contents())
+        TraverseOrder(node.get(), pre, post);
+      TraverseOrder(list->End(), pre, post);
+    } else if (root->AsLiteral()) {
+      // Nothing.
+    } else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) {
+      TraverseOrder(unaryop->operand(), pre, post);
+    } else if (root->AsBlockComment()) {
+      // Nothing.
+    } else if (root->AsEnd()) {
+      // Nothing.
+    } else {
+      CHECK(false) << "Unhandled case in TraverseOrder.";
+    }
+
+    post->push_back(root);
+  }
+}
+
+void Parser::AssignComments(ParseNode* file) {
+  // Start by generating a pre- and post- order traversal of the tree so we
+  // can determine what's before and after comments.
+  std::vector<const ParseNode*> pre;
+  std::vector<const ParseNode*> post;
+  TraverseOrder(file, &pre, &post);
+
+  // Assign line comments to syntax immediately following.
+  int cur_comment = 0;
+  for (auto* node : pre) {
+    if (node->GetRange().is_null()) {
+      CHECK_EQ(node, file) << "Only expected on top file node";
+      continue;
+    }
+    const Location start = node->GetRange().begin();
+    while (cur_comment < static_cast<int>(line_comment_tokens_.size())) {
+      if (start.byte() >= line_comment_tokens_[cur_comment].location().byte()) {
+        const_cast<ParseNode*>(node)->comments_mutable()->append_before(
+            line_comment_tokens_[cur_comment]);
+        ++cur_comment;
+      } else {
+        break;
+      }
+    }
+  }
+
+  // Remaining line comments go at end of file.
+  for (; cur_comment < static_cast<int>(line_comment_tokens_.size());
+       ++cur_comment)
+    file->comments_mutable()->append_after(line_comment_tokens_[cur_comment]);
+
+  // Assign suffix to syntax immediately before.
+  cur_comment = static_cast<int>(suffix_comment_tokens_.size() - 1);
+  for (std::vector<const ParseNode*>::const_reverse_iterator i = post.rbegin();
+       i != post.rend(); ++i) {
+    // Don't assign suffix comments to the function, list, or block, but instead
+    // to the last thing inside.
+    if ((*i)->AsFunctionCall() || (*i)->AsList() || (*i)->AsBlock())
+      continue;
+
+    Location start = (*i)->GetRange().begin();
+    Location end = (*i)->GetRange().end();
+
+    // Don't assign suffix comments to something that starts on an earlier
+    // line, so that in:
+    //
+    // sources = [ "a",
+    //     "b" ] # comment
+    //
+    // it's attached to "b", not sources = [ ... ].
+    if (start.line_number() != end.line_number())
+      continue;
+
+    while (cur_comment >= 0) {
+      if (end.byte() <= suffix_comment_tokens_[cur_comment].location().byte()) {
+        const_cast<ParseNode*>(*i)->comments_mutable()->append_suffix(
+            suffix_comment_tokens_[cur_comment]);
+        --cur_comment;
+      } else {
+        break;
+      }
+    }
+
+    // Suffix comments were assigned in reverse, so if there were multiple on
+    // the same node, they need to be reversed.
+    if ((*i)->comments() && !(*i)->comments()->suffix().empty())
+      const_cast<ParseNode*>(*i)->comments_mutable()->ReverseSuffix();
+  }
+}
+
+std::string IndentFor(int value) {
+  return std::string(value, ' ');
+}
+
+void RenderToText(const base::Value& node,
+                  int indent_level,
+                  std::ostringstream& os) {
+  const base::Value* child = node.FindKey(std::string("child"));
+  std::string node_type(node.FindKey("type")->GetString());
+  if (node_type == "ACCESSOR") {
+    // AccessorNode is a bit special, in that it holds a Token, not a ParseNode
+    // for the base.
+    os << IndentFor(indent_level) << node_type << std::endl;
+    os << IndentFor(indent_level + 1) << node.FindKey("value")->GetString()
+       << std::endl;
+  } else {
+    os << IndentFor(indent_level) << node_type;
+    if (node.FindKey("value")) {
+      os << "(" << node.FindKey("value")->GetString() << ")";
+    }
+    os << std::endl;
+  }
+  if (node.FindKey(kJsonBeforeComment)) {
+    for (auto& v : node.FindKey(kJsonBeforeComment)->GetList()) {
+      os << IndentFor(indent_level + 1) << "+BEFORE_COMMENT(\"" << v.GetString()
+         << "\")\n";
+    }
+  }
+  if (node.FindKey(kJsonSuffixComment)) {
+    for (auto& v : node.FindKey(kJsonSuffixComment)->GetList()) {
+      os << IndentFor(indent_level + 1) << "+SUFFIX_COMMENT(\"" << v.GetString()
+         << "\")\n";
+    }
+  }
+  if (node.FindKey(kJsonAfterComment)) {
+    for (auto& v : node.FindKey(kJsonAfterComment)->GetList()) {
+      os << IndentFor(indent_level + 1) << "+AFTER_COMMENT(\"" << v.GetString()
+         << "\")\n";
+    }
+  }
+  if (child) {
+    for (const base::Value& n : child->GetList()) {
+      RenderToText(n, indent_level + 1, os);
+    }
+  }
+  const base::Value* end = node.FindKey("end");
+  if (end &&
+      (end->FindKey(kJsonBeforeComment) || end->FindKey(kJsonSuffixComment) ||
+       end->FindKey(kJsonAfterComment))) {
+    RenderToText(*end, indent_level + 1, os);
+  }
+}
diff --git a/src/gn/parser.h b/src/gn/parser.h
new file mode 100644 (file)
index 0000000..1f1cf1a
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_PARSER_H_
+#define TOOLS_GN_PARSER_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "gn/err.h"
+#include "gn/parse_tree.h"
+
+extern const char kGrammar_Help[];
+
+struct ParserHelper;
+
+// Parses a series of tokens. The resulting AST will refer to the tokens passed
+// to the input, so the tokens an the file data they refer to must outlive your
+// use of the ParseNode.
+class Parser {
+ public:
+  // Will return a null pointer and set the err on error.
+  static std::unique_ptr<ParseNode> Parse(const std::vector<Token>& tokens,
+                                          Err* err);
+
+  // Alternative to parsing that assumes the input is an expression.
+  static std::unique_ptr<ParseNode> ParseExpression(
+      const std::vector<Token>& tokens,
+      Err* err);
+
+  // Alternative to parsing that assumes the input is a literal value.
+  static std::unique_ptr<ParseNode> ParseValue(const std::vector<Token>& tokens,
+                                               Err* err);
+
+ private:
+  // Vector must be valid for lifetime of call.
+  Parser(const std::vector<Token>& tokens, Err* err);
+  ~Parser();
+
+  std::unique_ptr<ParseNode> ParseExpression();
+
+  // Parses an expression with the given precedence or higher.
+  std::unique_ptr<ParseNode> ParseExpression(int precedence);
+
+  // |PrefixFunc|s used in parsing expressions.
+  std::unique_ptr<ParseNode> Block(const Token& token);
+  std::unique_ptr<ParseNode> Literal(const Token& token);
+  std::unique_ptr<ParseNode> Name(const Token& token);
+  std::unique_ptr<ParseNode> Group(const Token& token);
+  std::unique_ptr<ParseNode> Not(const Token& token);
+  std::unique_ptr<ParseNode> List(const Token& token);
+  std::unique_ptr<ParseNode> BlockComment(const Token& token);
+
+  // |InfixFunc|s used in parsing expressions.
+  std::unique_ptr<ParseNode> BinaryOperator(std::unique_ptr<ParseNode> left,
+                                            const Token& token);
+  std::unique_ptr<ParseNode> IdentifierOrCall(std::unique_ptr<ParseNode> left,
+                                              const Token& token);
+  std::unique_ptr<ParseNode> Assignment(std::unique_ptr<ParseNode> left,
+                                        const Token& token);
+  std::unique_ptr<ParseNode> Subscript(std::unique_ptr<ParseNode> left,
+                                       const Token& token);
+  std::unique_ptr<ParseNode> DotOperator(std::unique_ptr<ParseNode> left,
+                                         const Token& token);
+
+  // Helper to parse a comma separated list, optionally allowing trailing
+  // commas (allowed in [] lists, not in function calls).
+  std::unique_ptr<ListNode> ParseList(const Token& start_token,
+                                      Token::Type stop_before,
+                                      bool allow_trailing_comma);
+
+  std::unique_ptr<ParseNode> ParseFile();
+  std::unique_ptr<ParseNode> ParseStatement();
+  // Expects to be passed the token corresponding to the '{' and that the
+  // current token is the one following the '{'.
+  std::unique_ptr<BlockNode> ParseBlock(const Token& begin_brace,
+                                        BlockNode::ResultMode result_mode);
+  std::unique_ptr<ParseNode> ParseCondition();
+
+  // Generates a pre- and post-order traversal of the tree.
+  void TraverseOrder(const ParseNode* root,
+                     std::vector<const ParseNode*>* pre,
+                     std::vector<const ParseNode*>* post);
+
+  // Attach comments to nearby syntax.
+  void AssignComments(ParseNode* file);
+
+  bool IsAssignment(const ParseNode* node) const;
+  bool IsStatementBreak(Token::Type token_type) const;
+
+  bool LookAhead(Token::Type type);
+  bool Match(Token::Type type);
+  const Token& Consume(Token::Type type, const char* error_message);
+  const Token& Consume(Token::Type* types,
+                       size_t num_types,
+                       const char* error_message);
+  const Token& Consume();
+
+  // Call this only if !at_end().
+  const Token& cur_token() const { return tokens_[cur_]; }
+
+  const Token& cur_or_last_token() const {
+    return at_end() ? tokens_[tokens_.size() - 1] : cur_token();
+  }
+
+  bool done() const { return at_end() || has_error(); }
+  bool at_end() const { return cur_ >= tokens_.size(); }
+  bool has_error() const { return err_->has_error(); }
+
+  std::vector<Token> tokens_;
+  std::vector<Token> line_comment_tokens_;
+  std::vector<Token> suffix_comment_tokens_;
+
+  static ParserHelper expressions_[Token::NUM_TYPES];
+
+  Token invalid_token_;
+  Err* err_;
+
+  // Current index into the tokens.
+  size_t cur_;
+
+  FRIEND_TEST_ALL_PREFIXES(Parser, BinaryOp);
+  FRIEND_TEST_ALL_PREFIXES(Parser, Block);
+  FRIEND_TEST_ALL_PREFIXES(Parser, Condition);
+  FRIEND_TEST_ALL_PREFIXES(Parser, Expression);
+  FRIEND_TEST_ALL_PREFIXES(Parser, FunctionCall);
+  FRIEND_TEST_ALL_PREFIXES(Parser, List);
+  FRIEND_TEST_ALL_PREFIXES(Parser, ParenExpression);
+  FRIEND_TEST_ALL_PREFIXES(Parser, UnaryOp);
+
+  DISALLOW_COPY_AND_ASSIGN(Parser);
+};
+
+using PrefixFunc = std::unique_ptr<ParseNode> (Parser::*)(const Token& token);
+using InfixFunc = std::unique_ptr<ParseNode> (
+    Parser::*)(std::unique_ptr<ParseNode> left, const Token& token);
+
+struct ParserHelper {
+  PrefixFunc prefix;
+  InfixFunc infix;
+
+  // Used only for infix operators.
+  int precedence;
+};
+
+// Renders parse subtree as a formatted text, indenting by the given number of
+// spaces.
+void RenderToText(const base::Value& node,
+                  int indent_level,
+                  std::ostringstream& os);
+
+#endif  // TOOLS_GN_PARSER_H_
diff --git a/src/gn/parser_unittest.cc b/src/gn/parser_unittest.cc
new file mode 100644 (file)
index 0000000..8cfdb8d
--- /dev/null
@@ -0,0 +1,734 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "gn/input_file.h"
+#include "gn/parser.h"
+#include "gn/tokenizer.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool GetTokens(const InputFile* input, std::vector<Token>* result) {
+  result->clear();
+  Err err;
+  *result = Tokenizer::Tokenize(input, &err);
+  return !err.has_error();
+}
+
+void DoParserPrintTest(const char* input, const char* expected) {
+  std::vector<Token> tokens;
+  InputFile input_file(SourceFile("/test"));
+  input_file.SetContents(input);
+  ASSERT_TRUE(GetTokens(&input_file, &tokens));
+
+  Err err;
+  std::unique_ptr<ParseNode> result = Parser::Parse(tokens, &err);
+  if (!result)
+    err.PrintToStdout();
+  ASSERT_TRUE(result);
+
+  std::ostringstream collector;
+  RenderToText(result->GetJSONNode(), 0, collector);
+
+  EXPECT_EQ(expected, collector.str());
+}
+
+void DoExpressionPrintTest(const char* input, const char* expected) {
+  std::vector<Token> tokens;
+  InputFile input_file(SourceFile("/test"));
+  input_file.SetContents(input);
+  ASSERT_TRUE(GetTokens(&input_file, &tokens));
+
+  Err err;
+  std::unique_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
+  ASSERT_TRUE(result);
+
+  std::ostringstream collector;
+  RenderToText(result->GetJSONNode(), 0, collector);
+
+  EXPECT_EQ(expected, collector.str());
+}
+
+// Expects the tokenizer or parser to identify an error at the given line and
+// character.
+void DoParserErrorTest(const char* input, int err_line, int err_char) {
+  InputFile input_file(SourceFile("/test"));
+  input_file.SetContents(input);
+
+  Err err;
+  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
+  if (!err.has_error()) {
+    std::unique_ptr<ParseNode> result = Parser::Parse(tokens, &err);
+    ASSERT_FALSE(result);
+    ASSERT_TRUE(err.has_error());
+  }
+
+  EXPECT_EQ(err_line, err.location().line_number());
+  EXPECT_EQ(err_char, err.location().column_number());
+}
+
+// Expects the tokenizer or parser to identify an error at the given line and
+// character.
+void DoExpressionErrorTest(const char* input, int err_line, int err_char) {
+  InputFile input_file(SourceFile("/test"));
+  input_file.SetContents(input);
+
+  Err err;
+  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
+  if (!err.has_error()) {
+    std::unique_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
+    ASSERT_FALSE(result);
+    ASSERT_TRUE(err.has_error());
+  }
+
+  EXPECT_EQ(err_line, err.location().line_number());
+  EXPECT_EQ(err_char, err.location().column_number());
+}
+
+}  // namespace
+
+TEST(Parser, Literal) {
+  DoExpressionPrintTest("5", "LITERAL(5)\n");
+  DoExpressionPrintTest("\"stuff\"", "LITERAL(\"stuff\")\n");
+}
+
+TEST(Parser, BinaryOp) {
+  // TODO(scottmg): The tokenizer is dumb, and treats "5-1" as two integers,
+  // not a binary operator between two positive integers.
+  DoExpressionPrintTest("5 - 1",
+                        "BINARY(-)\n"
+                        " LITERAL(5)\n"
+                        " LITERAL(1)\n");
+  DoExpressionPrintTest("5+1",
+                        "BINARY(+)\n"
+                        " LITERAL(5)\n"
+                        " LITERAL(1)\n");
+  DoExpressionPrintTest("5 - 1 - 2",
+                        "BINARY(-)\n"
+                        " BINARY(-)\n"
+                        "  LITERAL(5)\n"
+                        "  LITERAL(1)\n"
+                        " LITERAL(2)\n");
+}
+
+TEST(Parser, FunctionCall) {
+  DoExpressionPrintTest("foo()",
+                        "FUNCTION(foo)\n"
+                        " LIST\n");
+  DoExpressionPrintTest("blah(1, 2)",
+                        "FUNCTION(blah)\n"
+                        " LIST\n"
+                        "  LITERAL(1)\n"
+                        "  LITERAL(2)\n");
+  DoExpressionErrorTest("foo(1, 2,)", 1, 10);
+  DoExpressionErrorTest("foo(1 2)", 1, 7);
+}
+
+TEST(Parser, ParenExpression) {
+  const char* input = "(foo(1)) + (a + (b - c) + d)";
+  const char* expected =
+      "BINARY(+)\n"
+      " FUNCTION(foo)\n"
+      "  LIST\n"
+      "   LITERAL(1)\n"
+      " BINARY(+)\n"
+      "  BINARY(+)\n"
+      "   IDENTIFIER(a)\n"
+      "   BINARY(-)\n"
+      "    IDENTIFIER(b)\n"
+      "    IDENTIFIER(c)\n"
+      "  IDENTIFIER(d)\n";
+  DoExpressionPrintTest(input, expected);
+  DoExpressionErrorTest("(a +", 1, 4);
+}
+
+TEST(Parser, OrderOfOperationsLeftAssociative) {
+  const char* input = "5 - 1 - 2\n";
+  const char* expected =
+      "BINARY(-)\n"
+      " BINARY(-)\n"
+      "  LITERAL(5)\n"
+      "  LITERAL(1)\n"
+      " LITERAL(2)\n";
+  DoExpressionPrintTest(input, expected);
+}
+
+TEST(Parser, OrderOfOperationsEqualityBoolean) {
+  const char* input =
+      "if (a == \"b\" && is_stuff) {\n"
+      "  print(\"hai\")\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " CONDITION\n"
+      "  BINARY(&&)\n"
+      "   BINARY(==)\n"
+      "    IDENTIFIER(a)\n"
+      "    LITERAL(\"b\")\n"
+      "   IDENTIFIER(is_stuff)\n"
+      "  BLOCK\n"
+      "   FUNCTION(print)\n"
+      "    LIST\n"
+      "     LITERAL(\"hai\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, UnaryOp) {
+  DoExpressionPrintTest("!foo",
+                        "UNARY(!)\n"
+                        " IDENTIFIER(foo)\n");
+
+  // No contents for binary operator.
+  DoExpressionErrorTest("a = !", 1, 5);
+}
+
+TEST(Parser, List) {
+  DoExpressionPrintTest("[]", "LIST\n");
+  DoExpressionPrintTest("[1,asd,]",
+                        "LIST\n"
+                        " LITERAL(1)\n"
+                        " IDENTIFIER(asd)\n");
+  DoExpressionPrintTest("[1, 2+3 - foo]",
+                        "LIST\n"
+                        " LITERAL(1)\n"
+                        " BINARY(-)\n"
+                        "  BINARY(+)\n"
+                        "   LITERAL(2)\n"
+                        "   LITERAL(3)\n"
+                        "  IDENTIFIER(foo)\n");
+  DoExpressionPrintTest("[1,\n2,\n 3,\n  4]",
+                        "LIST\n"
+                        " LITERAL(1)\n"
+                        " LITERAL(2)\n"
+                        " LITERAL(3)\n"
+                        " LITERAL(4)\n");
+
+  DoExpressionErrorTest("[a, 2+,]", 1, 7);
+  DoExpressionErrorTest("[,]", 1, 2);
+  DoExpressionErrorTest("[a,,]", 1, 4);
+}
+
+TEST(Parser, Assignment) {
+  DoParserPrintTest("a=2",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(a)\n"
+                    "  LITERAL(2)\n");
+
+  DoExpressionErrorTest("a = ", 1, 3);
+}
+
+TEST(Parser, Accessor) {
+  // Accessor indexing.
+  DoParserPrintTest("a=b[c+2]",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(a)\n"
+                    "  ACCESSOR\n"
+                    "   b\n"  // AccessorNode is a bit weird in that it holds
+                              // a Token, not a ParseNode for the base.
+                    "   BINARY(+)\n"
+                    "    IDENTIFIER(c)\n"
+                    "    LITERAL(2)\n");
+  DoParserErrorTest("a = b[1][0]", 1, 5);
+
+  // Member accessors.
+  DoParserPrintTest("a=b.c+2",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(a)\n"
+                    "  BINARY(+)\n"
+                    "   ACCESSOR\n"
+                    "    b\n"
+                    "    IDENTIFIER(c)\n"
+                    "   LITERAL(2)\n");
+  DoParserPrintTest("a.b = 5",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  ACCESSOR\n"
+                    "   a\n"
+                    "   IDENTIFIER(b)\n"
+                    "  LITERAL(5)\n");
+  DoParserErrorTest("a = b.c.d", 1, 6);  // Can't nest accessors (currently).
+
+  // Error at the bad dot in the RHS, not the + operator (crbug.com/472038).
+  DoParserErrorTest("foo(a + b.c.d)", 1, 10);
+}
+
+TEST(Parser, Condition) {
+  DoParserPrintTest("if(1) { a = 2 }",
+                    "BLOCK\n"
+                    " CONDITION\n"
+                    "  LITERAL(1)\n"
+                    "  BLOCK\n"
+                    "   BINARY(=)\n"
+                    "    IDENTIFIER(a)\n"
+                    "    LITERAL(2)\n");
+
+  DoParserPrintTest("if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }",
+                    "BLOCK\n"
+                    " CONDITION\n"
+                    "  LITERAL(1)\n"
+                    "  BLOCK\n"
+                    "   BINARY(=)\n"
+                    "    IDENTIFIER(a)\n"
+                    "    LITERAL(2)\n"
+                    "  CONDITION\n"
+                    "   LITERAL(0)\n"
+                    "   BLOCK\n"
+                    "    BINARY(=)\n"
+                    "     IDENTIFIER(a)\n"
+                    "     LITERAL(3)\n"
+                    "   BLOCK\n"
+                    "    BINARY(=)\n"
+                    "     IDENTIFIER(a)\n"
+                    "     LITERAL(4)\n");
+}
+
+TEST(Parser, OnlyCallAndAssignInBody) {
+  DoParserErrorTest("[]", 1, 2);
+  DoParserErrorTest("3 + 4", 1, 5);
+  DoParserErrorTest("6 - 7", 1, 5);
+  DoParserErrorTest("if (1) { 5 } else { print(4) }", 1, 12);
+}
+
+TEST(Parser, NoAssignmentInCondition) {
+  DoParserErrorTest("if (a=2) {}", 1, 5);
+}
+
+TEST(Parser, CompleteFunction) {
+  const char* input =
+      "cc_test(\"foo\") {\n"
+      "  sources = [\n"
+      "    \"foo.cc\",\n"
+      "    \"foo.h\"\n"
+      "  ]\n"
+      "  dependencies = [\n"
+      "    \"base\"\n"
+      "  ]\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(cc_test)\n"
+      "  LIST\n"
+      "   LITERAL(\"foo\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"foo.cc\")\n"
+      "     LITERAL(\"foo.h\")\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(dependencies)\n"
+      "    LIST\n"
+      "     LITERAL(\"base\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, FunctionWithConditional) {
+  const char* input =
+      "cc_test(\"foo\") {\n"
+      "  sources = [\"foo.cc\"]\n"
+      "  if (OS == \"mac\") {\n"
+      "    sources += \"bar.cc\"\n"
+      "  } else if (OS == \"win\") {\n"
+      "    sources -= [\"asd.cc\", \"foo.cc\"]\n"
+      "  } else {\n"
+      "    dependencies += [\"bar.cc\"]\n"
+      "  }\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(cc_test)\n"
+      "  LIST\n"
+      "   LITERAL(\"foo\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"foo.cc\")\n"
+      "   CONDITION\n"
+      "    BINARY(==)\n"
+      "     IDENTIFIER(OS)\n"
+      "     LITERAL(\"mac\")\n"
+      "    BLOCK\n"
+      "     BINARY(+=)\n"
+      "      IDENTIFIER(sources)\n"
+      "      LITERAL(\"bar.cc\")\n"
+      "    CONDITION\n"
+      "     BINARY(==)\n"
+      "      IDENTIFIER(OS)\n"
+      "      LITERAL(\"win\")\n"
+      "     BLOCK\n"
+      "      BINARY(-=)\n"
+      "       IDENTIFIER(sources)\n"
+      "       LIST\n"
+      "        LITERAL(\"asd.cc\")\n"
+      "        LITERAL(\"foo.cc\")\n"
+      "     BLOCK\n"
+      "      BINARY(+=)\n"
+      "       IDENTIFIER(dependencies)\n"
+      "       LIST\n"
+      "        LITERAL(\"bar.cc\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, UnterminatedBlock) {
+  DoParserErrorTest("stuff() {", 1, 9);
+}
+
+TEST(Parser, BadlyTerminatedNumber) {
+  DoParserErrorTest("1234z", 1, 5);
+}
+
+TEST(Parser, NewlinesInUnusualPlaces) {
+  DoParserPrintTest(
+      "if\n"
+      "(\n"
+      "a\n"
+      ")\n"
+      "{\n"
+      "}\n",
+      "BLOCK\n"
+      " CONDITION\n"
+      "  IDENTIFIER(a)\n"
+      "  BLOCK\n");
+}
+
+TEST(Parser, NewlinesInUnusualPlaces2) {
+  DoParserPrintTest("a\n=\n2\n",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(a)\n"
+                    "  LITERAL(2)\n");
+  DoParserPrintTest("x =\ny if\n(1\n) {}",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(x)\n"
+                    "  IDENTIFIER(y)\n"
+                    " CONDITION\n"
+                    "  LITERAL(1)\n"
+                    "  BLOCK\n");
+  DoParserPrintTest("x = 3\n+2",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(x)\n"
+                    "  BINARY(+)\n"
+                    "   LITERAL(3)\n"
+                    "   LITERAL(2)\n");
+}
+
+TEST(Parser, NewlineBeforeSubscript) {
+  const char* input = "a = b[1]";
+  const char* input_with_newline = "a = b\n[1]";
+  const char* expected =
+      "BLOCK\n"
+      " BINARY(=)\n"
+      "  IDENTIFIER(a)\n"
+      "  ACCESSOR\n"
+      "   b\n"
+      "   LITERAL(1)\n";
+  DoParserPrintTest(input, expected);
+  DoParserPrintTest(input_with_newline, expected);
+}
+
+TEST(Parser, SequenceOfExpressions) {
+  DoParserPrintTest("a = 1 b = 2",
+                    "BLOCK\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(a)\n"
+                    "  LITERAL(1)\n"
+                    " BINARY(=)\n"
+                    "  IDENTIFIER(b)\n"
+                    "  LITERAL(2)\n");
+}
+
+TEST(Parser, BlockAfterFunction) {
+  const char* input = "func(\"stuff\") {\n}";
+  // TODO(scottmg): Do we really want these to mean different things?
+  const char* input_with_newline = "func(\"stuff\")\n{\n}";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(func)\n"
+      "  LIST\n"
+      "   LITERAL(\"stuff\")\n"
+      "  BLOCK\n";
+  DoParserPrintTest(input, expected);
+  DoParserPrintTest(input_with_newline, expected);
+}
+
+TEST(Parser, LongExpression) {
+  const char* input = "a = b + c && d || e";
+  const char* expected =
+      "BLOCK\n"
+      " BINARY(=)\n"
+      "  IDENTIFIER(a)\n"
+      "  BINARY(||)\n"
+      "   BINARY(&&)\n"
+      "    BINARY(+)\n"
+      "     IDENTIFIER(b)\n"
+      "     IDENTIFIER(c)\n"
+      "    IDENTIFIER(d)\n"
+      "   IDENTIFIER(e)\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsStandalone) {
+  const char* input =
+      "# Toplevel comment.\n"
+      "\n"
+      "executable(\"wee\") {}\n";
+  const char* expected =
+      "BLOCK\n"
+      " BLOCK_COMMENT(# Toplevel comment.)\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "  BLOCK\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsStandaloneEof) {
+  const char* input =
+      "executable(\"wee\") {}\n"
+      "# EOF comment.\n";
+  const char* expected =
+      "BLOCK\n"
+      " +AFTER_COMMENT(\"# EOF comment.\")\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "  BLOCK\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsLineAttached) {
+  const char* input =
+      "executable(\"wee\") {\n"
+      "  # Some sources.\n"
+      "  sources = [\n"
+      "    \"stuff.cc\",\n"
+      "    \"things.cc\",\n"
+      "    # This file is special or something.\n"
+      "    \"another.cc\",\n"
+      "  ]\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    +BEFORE_COMMENT(\"# Some sources.\")\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"stuff.cc\")\n"
+      "     LITERAL(\"things.cc\")\n"
+      "     LITERAL(\"another.cc\")\n"
+      "      +BEFORE_COMMENT(\"# This file is special or something.\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsSuffix) {
+  const char* input =
+      "executable(\"wee\") { # This is some stuff.\n"
+      "sources = [ \"a.cc\" # And another comment here.\n"
+      "] }";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "   END())\n"
+      "    +SUFFIX_COMMENT(\"# This is some stuff.\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"a.cc\")\n"
+      "      +SUFFIX_COMMENT(\"# And another comment here.\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsSuffixDifferentLine) {
+  const char* input =
+      "executable(\"wee\") {\n"
+      "  sources = [ \"a\",\n"
+      "      \"b\" ] # Comment\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"a\")\n"
+      "     LITERAL(\"b\")\n"
+      "     END(])\n"
+      "      +SUFFIX_COMMENT(\"# Comment\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsSuffixMultiple) {
+  const char* input =
+      "executable(\"wee\") {\n"
+      "  sources = [\n"
+      "    \"a\",  # This is a comment,\n"
+      "          # and some more,\n"  // Note that this is aligned with above.
+      "          # then the end.\n"
+      "  ]\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(executable)\n"
+      "  LIST\n"
+      "   LITERAL(\"wee\")\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"a\")\n"
+      "      +SUFFIX_COMMENT(\"# This is a comment,\")\n"
+      "      +SUFFIX_COMMENT(\"# and some more,\")\n"
+      "      +SUFFIX_COMMENT(\"# then the end.\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsConnectedInList) {
+  const char* input =
+      "defines = [\n"
+      "\n"
+      "  # Connected comment.\n"
+      "  \"WEE\",\n"
+      "  \"BLORPY\",\n"
+      "]\n";
+  const char* expected =
+      "BLOCK\n"
+      " BINARY(=)\n"
+      "  IDENTIFIER(defines)\n"
+      "  LIST\n"
+      "   LITERAL(\"WEE\")\n"
+      "    +BEFORE_COMMENT(\"# Connected comment.\")\n"
+      "   LITERAL(\"BLORPY\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, CommentsAtEndOfBlock) {
+  const char* input =
+      "if (is_win) {\n"
+      "  sources = [\"a.cc\"]\n"
+      "  # Some comment at end.\n"
+      "}\n";
+  const char* expected =
+      "BLOCK\n"
+      " CONDITION\n"
+      "  IDENTIFIER(is_win)\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(sources)\n"
+      "    LIST\n"
+      "     LITERAL(\"a.cc\")\n"
+      "   END(})\n"
+      "    +BEFORE_COMMENT(\"# Some comment at end.\")\n";
+  DoParserPrintTest(input, expected);
+}
+
+// TODO(scottmg): I could be convinced this is incorrect. It's not clear to me
+// which thing this comment is intended to be attached to.
+TEST(Parser, CommentsEndOfBlockSingleLine) {
+  const char* input =
+      "defines = [ # EOL defines.\n"
+      "]\n";
+  const char* expected =
+      "BLOCK\n"
+      " BINARY(=)\n"
+      "  IDENTIFIER(defines)\n"
+      "   +SUFFIX_COMMENT(\"# EOL defines.\")\n"
+      "  LIST\n";
+  DoParserPrintTest(input, expected);
+}
+
+TEST(Parser, HangingIf) {
+  DoParserErrorTest("if", 1, 1);
+}
+
+TEST(Parser, NegatingList) {
+  DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30);
+}
+
+TEST(Parser, ConditionNoBracesIf) {
+  DoParserErrorTest(
+      "if (true)\n"
+      "  foreach(foo, []) {}\n"
+      "else {\n"
+      "  foreach(bar, []) {}\n"
+      "}\n",
+      2, 3);
+}
+
+TEST(Parser, ConditionNoBracesElse) {
+  DoParserErrorTest(
+      "if (true) {\n"
+      "  foreach(foo, []) {}\n"
+      "} else\n"
+      "  foreach(bar, []) {}\n",
+      4, 3);
+}
+
+TEST(Parser, ConditionNoBracesElseIf) {
+  DoParserErrorTest(
+      "if (true) {\n"
+      "  foreach(foo, []) {}\n"
+      "} else if (true)\n"
+      "  foreach(bar, []) {}\n",
+      4, 3);
+}
+
+// Disallow standalone {} for introducing new scopes. These are ambiguous with
+// target declarations (e.g. is:
+//   foo("bar") {}
+// a function with an associated block, or a standalone function with a
+// freestanding block.
+TEST(Parser, StandaloneBlock) {
+  // The error is reported at the end of the block when nothing is done
+  // with it. If we had said "a = { ..." then it would have been OK.
+  DoParserErrorTest(
+      "if (true) {\n"
+      "}\n"
+      "{\n"
+      "  assert(false)\n"
+      "}\n",
+      5, 1);
+}
+
+TEST(Parser, BlockValues) {
+  const char* input =
+      "print({a = 1 b = 2}, 3)\n"
+      "a = { b = \"asd\" }";
+  const char* expected =
+      "BLOCK\n"
+      " FUNCTION(print)\n"
+      "  LIST\n"
+      "   BLOCK\n"
+      "    BINARY(=)\n"
+      "     IDENTIFIER(a)\n"
+      "     LITERAL(1)\n"
+      "    BINARY(=)\n"
+      "     IDENTIFIER(b)\n"
+      "     LITERAL(2)\n"
+      "   LITERAL(3)\n"
+      " BINARY(=)\n"
+      "  IDENTIFIER(a)\n"
+      "  BLOCK\n"
+      "   BINARY(=)\n"
+      "    IDENTIFIER(b)\n"
+      "    LITERAL(\"asd\")\n";
+  DoParserPrintTest(input, expected);
+}
diff --git a/src/gn/path_output.cc b/src/gn/path_output.cc
new file mode 100644 (file)
index 0000000..c89e061
--- /dev/null
@@ -0,0 +1,176 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/path_output.h"
+
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/output_file.h"
+#include "gn/string_utils.h"
+#include "util/build_config.h"
+
+PathOutput::PathOutput(const SourceDir& current_dir,
+                       const std::string_view& source_root,
+                       EscapingMode escaping)
+    : current_dir_(current_dir) {
+  inverse_current_dir_ = RebasePath("//", current_dir, source_root);
+  if (!EndsWithSlash(inverse_current_dir_))
+    inverse_current_dir_.push_back('/');
+  options_.mode = escaping;
+}
+
+PathOutput::~PathOutput() = default;
+
+void PathOutput::WriteFile(std::ostream& out, const SourceFile& file) const {
+  WritePathStr(out, file.value());
+}
+
+void PathOutput::WriteDir(std::ostream& out,
+                          const SourceDir& dir,
+                          DirSlashEnding slash_ending) const {
+  if (dir.value() == "/") {
+    // Writing system root is always a slash (this will normally only come up
+    // on Posix systems).
+    if (slash_ending == DIR_NO_LAST_SLASH)
+      out << "/.";
+    else
+      out << "/";
+  } else if (dir.value() == "//") {
+    // Writing out the source root.
+    if (slash_ending == DIR_NO_LAST_SLASH) {
+      // The inverse_current_dir_ will contain a [back]slash at the end, so we
+      // can't just write it out.
+      if (inverse_current_dir_.empty()) {
+        out << ".";
+      } else {
+        out.write(inverse_current_dir_.c_str(),
+                  inverse_current_dir_.size() - 1);
+      }
+    } else {
+      if (inverse_current_dir_.empty())
+        out << "./";
+      else
+        out << inverse_current_dir_;
+    }
+  } else if (dir == current_dir_) {
+    // Writing the same directory. This needs special handling here since
+    // we need to output something else other than the input.
+    if (slash_ending == DIR_INCLUDE_LAST_SLASH)
+      out << "./";
+    else
+      out << ".";
+  } else if (slash_ending == DIR_INCLUDE_LAST_SLASH) {
+    WritePathStr(out, dir.value());
+  } else {
+    // DIR_NO_LAST_SLASH mode, just trim the last char.
+    WritePathStr(out,
+                 std::string_view(dir.value().data(), dir.value().size() - 1));
+  }
+}
+
+void PathOutput::WriteFile(std::ostream& out, const OutputFile& file) const {
+  // Here we assume that the path is already preprocessed.
+  EscapeStringToStream(out, file.value(), options_);
+}
+
+void PathOutput::WriteFiles(std::ostream& out,
+                            const std::vector<SourceFile>& files) const {
+  for (const auto& file : files) {
+    out << " ";
+    WriteFile(out, file);
+  }
+}
+
+void PathOutput::WriteFiles(std::ostream& out,
+                            const std::vector<OutputFile>& files) const {
+  for (const auto& file : files) {
+    out << " ";
+    WriteFile(out, file);
+  }
+}
+
+void PathOutput::WriteFiles(std::ostream& out,
+                            const UniqueVector<OutputFile>& files) const {
+  for (const auto& file : files) {
+    out << " ";
+    WriteFile(out, file);
+  }
+}
+
+void PathOutput::WriteDir(std::ostream& out,
+                          const OutputFile& file,
+                          DirSlashEnding slash_ending) const {
+  DCHECK(file.value().empty() || file.value()[file.value().size() - 1] == '/');
+
+  switch (slash_ending) {
+    case DIR_INCLUDE_LAST_SLASH:
+      EscapeStringToStream(out, file.value(), options_);
+      break;
+    case DIR_NO_LAST_SLASH:
+      if (!file.value().empty() &&
+          file.value()[file.value().size() - 1] == '/') {
+        // Trim trailing slash.
+        EscapeStringToStream(
+            out, std::string_view(file.value().data(), file.value().size() - 1),
+            options_);
+      } else {
+        // Doesn't end with a slash, write the whole thing.
+        EscapeStringToStream(out, file.value(), options_);
+      }
+      break;
+  }
+}
+
+void PathOutput::WriteFile(std::ostream& out,
+                           const base::FilePath& file) const {
+  // Assume native file paths are always absolute.
+  EscapeStringToStream(out, FilePathToUTF8(file), options_);
+}
+
+void PathOutput::WriteSourceRelativeString(std::ostream& out,
+                                           const std::string_view& str) const {
+  if (options_.mode == ESCAPE_NINJA_COMMAND) {
+    // Shell escaping needs an intermediate string since it may end up
+    // quoting the whole thing.
+    std::string intermediate;
+    intermediate.reserve(inverse_current_dir_.size() + str.size());
+    intermediate.assign(inverse_current_dir_.c_str(),
+                        inverse_current_dir_.size());
+    intermediate.append(str.data(), str.size());
+
+    EscapeStringToStream(
+        out, std::string_view(intermediate.c_str(), intermediate.size()),
+        options_);
+  } else {
+    // Ninja (and none) escaping can avoid the intermediate string and
+    // reprocessing of the inverse_current_dir_.
+    out << inverse_current_dir_;
+    EscapeStringToStream(out, str, options_);
+  }
+}
+
+void PathOutput::WritePathStr(std::ostream& out,
+                              const std::string_view& str) const {
+  DCHECK(str.size() > 0 && str[0] == '/');
+
+  if (str.substr(0, current_dir_.value().size()) ==
+      std::string_view(current_dir_.value())) {
+    // The current dir is a prefix of the output file, so we can strip the
+    // prefix and write out the result.
+    EscapeStringToStream(out, str.substr(current_dir_.value().size()),
+                         options_);
+  } else if (str.size() >= 2 && str[1] == '/') {
+    WriteSourceRelativeString(out, str.substr(2));
+  } else {
+// Input begins with one slash, don't write the current directory since
+// it's system-absolute.
+#if defined(OS_WIN)
+    // On Windows, trim the leading slash, since the input for absolute
+    // paths will look like "/C:/foo/bar.txt".
+    EscapeStringToStream(out, str.substr(1), options_);
+#else
+    EscapeStringToStream(out, str, options_);
+#endif
+  }
+}
diff --git a/src/gn/path_output.h b/src/gn/path_output.h
new file mode 100644 (file)
index 0000000..729ba98
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_PATH_OUTPUT_H_
+#define TOOLS_GN_PATH_OUTPUT_H_
+
+#include <iosfwd>
+#include <string>
+#include <string_view>
+
+#include "base/macros.h"
+#include "gn/escape.h"
+#include "gn/source_dir.h"
+#include "gn/unique_vector.h"
+
+class OutputFile;
+class SourceFile;
+
+namespace base {
+class FilePath;
+}
+
+// Writes file names to streams assuming a certain input directory and
+// escaping rules. This gives us a central place for managing this state.
+class PathOutput {
+ public:
+  // Controls whether writing directory names include the trailing slash.
+  // Often we don't want the trailing slash when writing out to a command line,
+  // especially on Windows where it's a backslash and might be interpreted as
+  // escaping the thing following it.
+  enum DirSlashEnding {
+    DIR_INCLUDE_LAST_SLASH,
+    DIR_NO_LAST_SLASH,
+  };
+
+  PathOutput(const SourceDir& current_dir,
+             const std::string_view& source_root,
+             EscapingMode escaping);
+  ~PathOutput();
+
+  // Read-only since inverse_current_dir_ is computed depending on this.
+  EscapingMode escaping_mode() const { return options_.mode; }
+
+  const SourceDir& current_dir() const { return current_dir_; }
+
+  // Getter/setters for flags inside the escape options.
+  bool inhibit_quoting() const { return options_.inhibit_quoting; }
+  void set_inhibit_quoting(bool iq) { options_.inhibit_quoting = iq; }
+  void set_escape_platform(EscapingPlatform p) { options_.platform = p; }
+
+  void WriteFile(std::ostream& out, const SourceFile& file) const;
+  void WriteFile(std::ostream& out, const OutputFile& file) const;
+  void WriteFile(std::ostream& out, const base::FilePath& file) const;
+
+  // Writes the given SourceFiles/OutputFiles with spaces separating them. This
+  // will also write an initial space before the first item.
+  void WriteFiles(std::ostream& out, const std::vector<SourceFile>& file) const;
+  void WriteFiles(std::ostream& out,
+                  const std::vector<OutputFile>& files) const;
+  void WriteFiles(std::ostream& out,
+                  const UniqueVector<OutputFile>& files) const;
+
+  // This variant assumes the dir ends in a trailing slash or is empty.
+  void WriteDir(std::ostream& out,
+                const SourceDir& dir,
+                DirSlashEnding slash_ending) const;
+
+  void WriteDir(std::ostream& out,
+                const OutputFile& file,
+                DirSlashEnding slash_ending) const;
+
+  // Backend for WriteFile and WriteDir. This appends the given file or
+  // directory string to the file.
+  void WritePathStr(std::ostream& out, const std::string_view& str) const;
+
+ private:
+  // Takes the given string and writes it out, appending to the inverse
+  // current dir. This assumes leading slashes have been trimmed.
+  void WriteSourceRelativeString(std::ostream& out,
+                                 const std::string_view& str) const;
+
+  SourceDir current_dir_;
+
+  // Uses system slashes if convert_slashes_to_system_.
+  std::string inverse_current_dir_;
+
+  // Since the inverse_current_dir_ depends on some of these, we don't expose
+  // this directly to modification.
+  EscapeOptions options_;
+};
+
+#endif  // TOOLS_GN_PATH_OUTPUT_H_
diff --git a/src/gn/path_output_unittest.cc b/src/gn/path_output_unittest.cc
new file mode 100644 (file)
index 0000000..1debb64
--- /dev/null
@@ -0,0 +1,276 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "base/files/file_path.h"
+#include "gn/output_file.h"
+#include "gn/path_output.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(PathOutput, Basic) {
+  SourceDir build_dir("//out/Debug/");
+  std::string_view source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NONE);
+  {
+    // Normal source-root path.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/bar.cc"));
+    EXPECT_EQ("../../foo/bar.cc", out.str());
+  }
+  {
+    // File in the root dir.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo.cc"));
+    EXPECT_EQ("../../foo.cc", out.str());
+  }
+  {
+    // Files in the output dir.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//out/Debug/foo.cc"));
+    out << " ";
+    writer.WriteFile(out, SourceFile("//out/Debug/bar/baz.cc"));
+    EXPECT_EQ("foo.cc bar/baz.cc", out.str());
+  }
+#if defined(OS_WIN)
+  {
+    // System-absolute path.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("/C:/foo/bar.cc"));
+    EXPECT_EQ("C:/foo/bar.cc", out.str());
+  }
+#else
+  {
+    // System-absolute path.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("/foo/bar.cc"));
+    EXPECT_EQ("/foo/bar.cc", out.str());
+  }
+#endif
+}
+
+// Same as basic but the output dir is the root.
+TEST(PathOutput, BasicInRoot) {
+  SourceDir build_dir("//");
+  std::string_view source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NONE);
+  {
+    // Normal source-root path.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/bar.cc"));
+    EXPECT_EQ("foo/bar.cc", out.str());
+  }
+  {
+    // File in the root dir.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo.cc"));
+    EXPECT_EQ("foo.cc", out.str());
+  }
+}
+
+TEST(PathOutput, NinjaEscaping) {
+  SourceDir build_dir("//out/Debug/");
+  std::string_view source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA);
+  {
+    // Spaces and $ in filenames.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/foo bar$.cc"));
+    EXPECT_EQ("../../foo/foo$ bar$$.cc", out.str());
+  }
+  {
+    // Not other weird stuff
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/\"foo\".cc"));
+    EXPECT_EQ("../../foo/\"foo\".cc", out.str());
+  }
+}
+
+TEST(PathOutput, NinjaForkEscaping) {
+  SourceDir build_dir("//out/Debug/");
+  std::string_view source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA_COMMAND);
+
+  // Spaces in filenames should get quoted on Windows.
+  writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/foo bar.cc"));
+    EXPECT_EQ("\"../../foo/foo$ bar.cc\"", out.str());
+  }
+
+  // Spaces in filenames should get escaped on Posix.
+  writer.set_escape_platform(ESCAPE_PLATFORM_POSIX);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/foo bar.cc"));
+    EXPECT_EQ("../../foo/foo\\$ bar.cc", out.str());
+  }
+
+  // Quotes should get blackslash-escaped on Windows and Posix.
+  writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/\"foobar\".cc"));
+    // Our Windows code currently quotes the whole thing in this case for
+    // code simplicity, even though it's strictly unnecessary. This might
+    // change in the future.
+    EXPECT_EQ("\"../../foo/\\\"foobar\\\".cc\"", out.str());
+  }
+  writer.set_escape_platform(ESCAPE_PLATFORM_POSIX);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/\"foobar\".cc"));
+    EXPECT_EQ("../../foo/\\\"foobar\\\".cc", out.str());
+  }
+
+  // Backslashes should get escaped on non-Windows and preserved on Windows.
+  writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, OutputFile("foo\\bar.cc"));
+    EXPECT_EQ("foo\\bar.cc", out.str());
+  }
+  writer.set_escape_platform(ESCAPE_PLATFORM_POSIX);
+  {
+    std::ostringstream out;
+    writer.WriteFile(out, OutputFile("foo\\bar.cc"));
+    EXPECT_EQ("foo\\\\bar.cc", out.str());
+  }
+}
+
+TEST(PathOutput, InhibitQuoting) {
+  SourceDir build_dir("//out/Debug/");
+  std::string_view source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA_COMMAND);
+  writer.set_inhibit_quoting(true);
+
+  writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
+  {
+    // We should get unescaped spaces in the output with no quotes.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/foo bar.cc"));
+    EXPECT_EQ("../../foo/foo$ bar.cc", out.str());
+  }
+
+  writer.set_escape_platform(ESCAPE_PLATFORM_POSIX);
+  {
+    // Escapes the space.
+    std::ostringstream out;
+    writer.WriteFile(out, SourceFile("//foo/foo bar.cc"));
+    EXPECT_EQ("../../foo/foo\\$ bar.cc", out.str());
+  }
+}
+
+TEST(PathOutput, WriteDir) {
+  {
+    SourceDir build_dir("//out/Debug/");
+    std::string_view source_root("/source/root");
+    PathOutput writer(build_dir, source_root, ESCAPE_NINJA);
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//foo/bar/"),
+                      PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("../../foo/bar/", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//foo/bar/"),
+                      PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ("../../foo/bar", out.str());
+    }
+
+    // Output source root dir.
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//"), PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("../../", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//"), PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ("../..", out.str());
+    }
+
+    // Output system root dir.
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("/"), PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("/", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("/"), PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("/", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("/"), PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ("/.", out.str());
+    }
+
+    // Output inside current dir.
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//out/Debug/"),
+                      PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("./", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//out/Debug/"),
+                      PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ(".", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//out/Debug/foo/"),
+                      PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("foo/", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, SourceDir("//out/Debug/foo/"),
+                      PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ("foo", out.str());
+    }
+
+    // WriteDir using an OutputFile.
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, OutputFile("foo/"),
+                      PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("foo/", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, OutputFile("foo/"), PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ("foo", out.str());
+    }
+    {
+      std::ostringstream out;
+      writer.WriteDir(out, OutputFile(), PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("", out.str());
+    }
+  }
+  {
+    // Empty build dir writer.
+    std::string_view source_root("/source/root");
+    PathOutput root_writer(SourceDir("//"), source_root, ESCAPE_NINJA);
+    {
+      std::ostringstream out;
+      root_writer.WriteDir(out, SourceDir("//"),
+                           PathOutput::DIR_INCLUDE_LAST_SLASH);
+      EXPECT_EQ("./", out.str());
+    }
+    {
+      std::ostringstream out;
+      root_writer.WriteDir(out, SourceDir("//"), PathOutput::DIR_NO_LAST_SLASH);
+      EXPECT_EQ(".", out.str());
+    }
+  }
+}
diff --git a/src/gn/pattern.cc b/src/gn/pattern.cc
new file mode 100644 (file)
index 0000000..cf03bab
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/pattern.h"
+
+#include "gn/value.h"
+
+const char kFilePattern_Help[] =
+    R"*(File patterns
+
+  File patterns are VERY limited regular expressions. They must match the
+  entire input string to be counted as a match. In regular expression parlance,
+  there is an implicit "^...$" surrounding your input. If you want to match a
+  substring, you need to use wildcards at the beginning and end.
+
+  There are only two special tokens understood by the pattern matcher.
+  Everything else is a literal.
+
+   - "*" Matches zero or more of any character. It does not depend on the
+     preceding character (in regular expression parlance it is equivalent to
+     ".*").
+
+   - "\b" Matches a path boundary. This will match the beginning or end of a
+     string, or a slash.
+
+Pattern examples
+
+  "*asdf*"
+      Matches a string containing "asdf" anywhere.
+
+  "asdf"
+      Matches only the exact string "asdf".
+
+  "*.cc"
+      Matches strings ending in the literal ".cc".
+
+  "\bwin/*"
+      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+
+)*";
+
+namespace {
+
+void ParsePattern(const std::string& s, std::vector<Pattern::Subrange>* out) {
+  // Set when the last subrange is a literal so we can just append when we
+  // find another literal.
+  Pattern::Subrange* last_literal = nullptr;
+
+  for (size_t i = 0; i < s.size(); i++) {
+    if (s[i] == '*') {
+      // Don't allow two **.
+      if (out->size() == 0 ||
+          (*out)[out->size() - 1].type != Pattern::Subrange::ANYTHING)
+        out->push_back(Pattern::Subrange(Pattern::Subrange::ANYTHING));
+      last_literal = nullptr;
+    } else if (s[i] == '\\') {
+      if (i < s.size() - 1 && s[i + 1] == 'b') {
+        // "\b" means path boundary.
+        i++;
+        out->push_back(Pattern::Subrange(Pattern::Subrange::PATH_BOUNDARY));
+        last_literal = nullptr;
+      } else {
+        // Backslash + anything else means that literal char.
+        if (!last_literal) {
+          out->push_back(Pattern::Subrange(Pattern::Subrange::LITERAL));
+          last_literal = &(*out)[out->size() - 1];
+        }
+        if (i < s.size() - 1) {
+          i++;
+          last_literal->literal.push_back(s[i]);
+        } else {
+          // Single backslash at end, use literal backslash.
+          last_literal->literal.push_back('\\');
+        }
+      }
+    } else {
+      if (!last_literal) {
+        out->push_back(Pattern::Subrange(Pattern::Subrange::LITERAL));
+        last_literal = &(*out)[out->size() - 1];
+      }
+      last_literal->literal.push_back(s[i]);
+    }
+  }
+}
+
+}  // namespace
+
+Pattern::Pattern(const std::string& s) {
+  ParsePattern(s, &subranges_);
+  is_suffix_ =
+      (subranges_.size() == 2 && subranges_[0].type == Subrange::ANYTHING &&
+       subranges_[1].type == Subrange::LITERAL);
+}
+
+Pattern::Pattern(const Pattern& other) = default;
+
+Pattern::~Pattern() = default;
+
+bool Pattern::MatchesString(const std::string& s) const {
+  // Empty pattern matches only empty string.
+  if (subranges_.empty())
+    return s.empty();
+
+  if (is_suffix_) {
+    const std::string& suffix = subranges_[1].literal;
+    if (suffix.size() > s.size())
+      return false;  // Too short.
+    return s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
+  }
+
+  return RecursiveMatch(s, 0, 0, true);
+}
+
+// We assume the number of ranges is small so recursive is always reasonable.
+// Could be optimized to only be recursive for *.
+bool Pattern::RecursiveMatch(const std::string& s,
+                             size_t begin_char,
+                             size_t subrange_index,
+                             bool allow_implicit_path_boundary) const {
+  if (subrange_index >= subranges_.size()) {
+    // Hit the end of our subranges, the text should also be at the end for a
+    // match.
+    return begin_char == s.size();
+  }
+
+  const Subrange& sr = subranges_[subrange_index];
+  switch (sr.type) {
+    case Subrange::LITERAL: {
+      if (s.size() - begin_char < sr.literal.size())
+        return false;  // Not enough room.
+      if (s.compare(begin_char, sr.literal.size(), sr.literal) != 0)
+        return false;  // Literal doesn't match.
+
+      // Recursively check the next one.
+      return RecursiveMatch(s, begin_char + sr.literal.size(),
+                            subrange_index + 1, true);
+    }
+
+    case Subrange::PATH_BOUNDARY: {
+      // When we can accept an implicit path boundary, we have to check both
+      // a match of the literal and the implicit one.
+      if (allow_implicit_path_boundary &&
+          (begin_char == 0 || begin_char == s.size())) {
+        // At implicit path boundary, see if the rest of the pattern matches.
+        if (RecursiveMatch(s, begin_char, subrange_index + 1, false))
+          return true;
+      }
+
+      // Check for a literal "/".
+      if (begin_char < s.size() && s[begin_char] == '/') {
+        // At explicit boundary, see if the rest of the pattern matches.
+        if (RecursiveMatch(s, begin_char + 1, subrange_index + 1, true))
+          return true;
+      }
+      return false;
+    }
+
+    case Subrange::ANYTHING: {
+      if (subrange_index == subranges_.size() - 1)
+        return true;  // * at the end, consider it matching.
+
+      size_t min_next_size = sr.MinSize();
+
+      // We don't care about exactly what matched as long as there was a match,
+      // so we can do this front-to-back. If we needed the match, we would
+      // normally want "*" to be greedy so would work backwards.
+      for (size_t i = begin_char; i < s.size() - min_next_size; i++) {
+        // Note: this could probably be faster by detecting the type of the
+        // next match in advance and checking for a match in this loop rather
+        // than doing a full recursive call for each character.
+        if (RecursiveMatch(s, i, subrange_index + 1, true))
+          return true;
+      }
+      return false;
+    }
+
+    default:
+      NOTREACHED();
+  }
+
+  return false;
+}
+
+PatternList::PatternList() = default;
+
+PatternList::PatternList(const PatternList& other) = default;
+
+PatternList::~PatternList() = default;
+
+void PatternList::Append(const Pattern& pattern) {
+  patterns_.push_back(pattern);
+}
+
+void PatternList::SetFromValue(const Value& v, Err* err) {
+  patterns_.clear();
+
+  if (v.type() != Value::LIST) {
+    *err = Err(v.origin(), "This value must be a list.");
+    return;
+  }
+
+  const std::vector<Value>& list = v.list_value();
+  for (const auto& elem : list) {
+    if (!elem.VerifyTypeIs(Value::STRING, err))
+      return;
+    patterns_.push_back(Pattern(elem.string_value()));
+  }
+}
+
+bool PatternList::MatchesString(const std::string& s) const {
+  for (const auto& pattern : patterns_) {
+    if (pattern.MatchesString(s))
+      return true;
+  }
+  return false;
+}
+
+bool PatternList::MatchesValue(const Value& v) const {
+  if (v.type() == Value::STRING)
+    return MatchesString(v.string_value());
+  return false;
+}
diff --git a/src/gn/pattern.h b/src/gn/pattern.h
new file mode 100644 (file)
index 0000000..f833ca4
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_PATTERN_H_
+#define TOOLS_GN_PATTERN_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "gn/value.h"
+
+extern const char kFilePattern_Help[];
+
+class Pattern {
+ public:
+  struct Subrange {
+    enum Type {
+      LITERAL,       // Matches exactly the contents of the string.
+      ANYTHING,      // * (zero or more chars).
+      PATH_BOUNDARY  // '/' or beginning of string.
+    };
+
+    explicit Subrange(Type t, const std::string& l = std::string())
+        : type(t), literal(l) {}
+
+    // Returns the minimum number of chars that this subrange requires.
+    size_t MinSize() const {
+      switch (type) {
+        case LITERAL:
+          return literal.size();
+        case ANYTHING:
+          return 0;
+        case PATH_BOUNDARY:
+          return 0;  // Can match beginning or end of string, which is 0 len.
+        default:
+          return 0;
+      }
+    }
+
+    Type type;
+
+    // When type == LITERAL this is the text to match.
+    std::string literal;
+  };
+
+  explicit Pattern(const std::string& s);
+  Pattern(const Pattern& other);
+  ~Pattern();
+
+  // Returns true if the current pattern matches the given string.
+  bool MatchesString(const std::string& s) const;
+
+ private:
+  // allow_implicit_path_boundary determines if a path boundary should accept
+  // matches at the beginning or end of the string.
+  bool RecursiveMatch(const std::string& s,
+                      size_t begin_char,
+                      size_t subrange_index,
+                      bool allow_implicit_path_boundary) const;
+
+  std::vector<Subrange> subranges_;
+
+  // Set to true when the subranges are "*foo" ("ANYTHING" followed by a
+  // literal). This covers most patterns so we optimize for this.
+  bool is_suffix_;
+};
+
+class PatternList {
+ public:
+  PatternList();
+  PatternList(const PatternList& other);
+  ~PatternList();
+
+  bool is_empty() const { return patterns_.empty(); }
+
+  void Append(const Pattern& pattern);
+
+  // Initializes the pattern list from a give list of pattern strings. Sets
+  // |*err| on failure.
+  void SetFromValue(const Value& v, Err* err);
+
+  bool MatchesString(const std::string& s) const;
+  bool MatchesValue(const Value& v) const;
+
+ private:
+  std::vector<Pattern> patterns_;
+};
+
+#endif  // TOOLS_GN_PATTERN_H_
diff --git a/src/gn/pattern_unittest.cc b/src/gn/pattern_unittest.cc
new file mode 100644 (file)
index 0000000..d796289
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <iterator>
+
+#include "base/macros.h"
+#include "gn/pattern.h"
+#include "util/test/test.h"
+
+namespace {
+
+struct Case {
+  const char* pattern;
+  const char* candidate;
+  bool expected_match;
+};
+
+}  // namespace
+
+TEST(Pattern, Matches) {
+  Case pattern_cases[] = {
+      // Empty pattern matches only empty string.
+      {"", "", true},
+      {"", "foo", false},
+      // Exact matches.
+      {"foo", "foo", true},
+      {"foo", "bar", false},
+      // Path boundaries.
+      {"\\b", "", true},
+      {"\\b", "/", true},
+      {"\\b\\b", "/", true},
+      {"\\b\\b\\b", "", false},
+      {"\\b\\b\\b", "/", true},
+      {"\\b", "//", false},
+      {"\\bfoo\\b", "foo", true},
+      {"\\bfoo\\b", "/foo/", true},
+      {"\\b\\bfoo", "/foo", true},
+      // *
+      {"*", "", true},
+      {"*", "foo", true},
+      {"*foo", "foo", true},
+      {"*foo", "gagafoo", true},
+      {"*foo", "gagafoob", false},
+      {"foo*bar", "foobar", true},
+      {"foo*bar", "foo-bar", true},
+      {"foo*bar", "foolalalalabar", true},
+      {"foo*bar", "foolalalalabaz", false},
+      {"*a*b*c*d*", "abcd", true},
+      {"*a*b*c*d*", "1a2b3c4d5", true},
+      {"*a*b*c*d*", "1a2b3c45", false},
+      {"*\\bfoo\\b*", "foo", true},
+      {"*\\bfoo\\b*", "/foo/", true},
+      {"*\\bfoo\\b*", "foob", false},
+      {"*\\bfoo\\b*", "lala/foo/bar/baz", true},
+  };
+  for (size_t i = 0; i < std::size(pattern_cases); i++) {
+    const Case& c = pattern_cases[i];
+    Pattern pattern(c.pattern);
+    bool result = pattern.MatchesString(c.candidate);
+    EXPECT_EQ(c.expected_match, result)
+        << i << ": \"" << c.pattern << "\", \"" << c.candidate << "\"";
+  }
+}
diff --git a/src/gn/pool.cc b/src/gn/pool.cc
new file mode 100644 (file)
index 0000000..60049a1
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/pool.h"
+
+#include <sstream>
+
+#include "base/logging.h"
+
+Pool::~Pool() = default;
+
+Pool* Pool::AsPool() {
+  return this;
+}
+
+const Pool* Pool::AsPool() const {
+  return this;
+}
+
+std::string Pool::GetNinjaName(const Label& default_toolchain) const {
+  bool include_toolchain = label().toolchain_dir() != default_toolchain.dir() ||
+                           label().toolchain_name() != default_toolchain.name();
+  return GetNinjaName(include_toolchain);
+}
+
+std::string Pool::GetNinjaName(bool include_toolchain) const {
+  std::ostringstream buffer;
+  if (include_toolchain) {
+    DCHECK(label().toolchain_dir().is_source_absolute());
+    std::string toolchain_dir = label().toolchain_dir().value();
+    for (std::string::size_type i = 2; i < toolchain_dir.size(); ++i) {
+      buffer << (toolchain_dir[i] == '/' ? '_' : toolchain_dir[i]);
+    }
+    buffer << label().toolchain_name() << "_";
+  }
+
+  DCHECK(label().dir().is_source_absolute());
+  std::string label_dir = label().dir().value();
+  for (std::string::size_type i = 2; i < label_dir.size(); ++i) {
+    buffer << (label_dir[i] == '/' ? '_' : label_dir[i]);
+  }
+  buffer << label().name();
+  return buffer.str();
+}
diff --git a/src/gn/pool.h b/src/gn/pool.h
new file mode 100644 (file)
index 0000000..3d6f8f3
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_POOL_H_
+#define TOOLS_GN_POOL_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "gn/item.h"
+
+// Represents a named pool in the dependency graph.
+//
+// A pool is used to limit the parallelism of task invocation in the
+// generated ninja build. Pools are referenced by toolchains.
+class Pool : public Item {
+ public:
+  using Item::Item;
+  ~Pool() override;
+
+  // Item implementation.
+  Pool* AsPool() override;
+  const Pool* AsPool() const override;
+
+  // The pool depth (number of task to run simultaneously).
+  int64_t depth() const { return depth_; }
+  void set_depth(int64_t depth) { depth_ = depth; }
+
+  // The pool name in generated ninja files.
+  std::string GetNinjaName(const Label& default_toolchain) const;
+
+ private:
+  std::string GetNinjaName(bool include_toolchain) const;
+
+  int64_t depth_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(Pool);
+};
+
+#endif  // TOOLS_GN_POOL_H_
diff --git a/src/gn/qt_creator_writer.cc b/src/gn/qt_creator_writer.cc
new file mode 100644 (file)
index 0000000..f5c997a
--- /dev/null
@@ -0,0 +1,296 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/qt_creator_writer.h"
+
+#include <optional>
+#include <set>
+#include <sstream>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+#include "gn/builder.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/label.h"
+#include "gn/loader.h"
+
+namespace {
+base::FilePath::CharType kProjectDirName[] =
+    FILE_PATH_LITERAL("qtcreator_project");
+base::FilePath::CharType kProjectName[] = FILE_PATH_LITERAL("all");
+base::FilePath::CharType kMainProjectFileSuffix[] =
+    FILE_PATH_LITERAL(".creator");
+base::FilePath::CharType kSourcesFileSuffix[] = FILE_PATH_LITERAL(".files");
+base::FilePath::CharType kIncludesFileSuffix[] = FILE_PATH_LITERAL(".includes");
+base::FilePath::CharType kDefinesFileSuffix[] = FILE_PATH_LITERAL(".config");
+}  // namespace
+
+// static
+bool QtCreatorWriter::RunAndWriteFile(const BuildSettings* build_settings,
+                                      const Builder& builder,
+                                      Err* err,
+                                      const std::string& root_target) {
+  base::FilePath project_dir =
+      build_settings->GetFullPath(build_settings->build_dir())
+          .Append(kProjectDirName);
+  if (!base::DirectoryExists(project_dir)) {
+    base::File::Error error;
+    if (!base::CreateDirectoryAndGetError(project_dir, &error)) {
+      *err =
+          Err(Location(), "Could not create the QtCreator project directory '" +
+                              FilePathToUTF8(project_dir) +
+                              "': " + base::File::ErrorToString(error));
+      return false;
+    }
+  }
+
+  base::FilePath project_prefix = project_dir.Append(kProjectName);
+  QtCreatorWriter gen(build_settings, builder, project_prefix, root_target);
+  gen.Run();
+  if (gen.err_.has_error()) {
+    *err = gen.err_;
+    return false;
+  }
+  return true;
+}
+
+QtCreatorWriter::QtCreatorWriter(const BuildSettings* build_settings,
+                                 const Builder& builder,
+                                 const base::FilePath& project_prefix,
+                                 const std::string& root_target_name)
+    : build_settings_(build_settings),
+      builder_(builder),
+      project_prefix_(project_prefix),
+      root_target_name_(root_target_name) {}
+
+QtCreatorWriter::~QtCreatorWriter() = default;
+
+void QtCreatorWriter::CollectDeps(const Target* target) {
+  for (const auto& dep : target->GetDeps(Target::DEPS_ALL)) {
+    const Target* dep_target = dep.ptr;
+    if (targets_.count(dep_target))
+      continue;
+    targets_.insert(dep_target);
+    CollectDeps(dep_target);
+  }
+}
+
+bool QtCreatorWriter::DiscoverTargets() {
+  auto all_targets = builder_.GetAllResolvedTargets();
+
+  if (root_target_name_.empty()) {
+    targets_ = std::set<const Target*>(all_targets.begin(), all_targets.end());
+    return true;
+  }
+
+  const Target* root_target = nullptr;
+  for (const Target* target : all_targets) {
+    if (target->label().name() == root_target_name_) {
+      root_target = target;
+      break;
+    }
+  }
+
+  if (!root_target) {
+    err_ = Err(Location(), "Target '" + root_target_name_ + "' not found.");
+    return false;
+  }
+
+  targets_.insert(root_target);
+  CollectDeps(root_target);
+  return true;
+}
+
+void QtCreatorWriter::AddToSources(const Target::FileList& files) {
+  for (const SourceFile& file : files) {
+    const std::string& file_path =
+        FilePathToUTF8(build_settings_->GetFullPath(file));
+    sources_.insert(file_path);
+  }
+}
+
+namespace QtCreatorWriterUtils {
+
+enum class CVersion {
+  C99,
+  C11,
+};
+
+enum class CxxVersion {
+  CXX98,
+  CXX03,
+  CXX11,
+  CXX14,
+  CXX17,
+};
+
+std::string ToMacro(CVersion version) {
+  const std::string s = "__STDC_VERSION__";
+
+  switch (version) {
+    case CVersion::C99:
+      return s + " 199901L";
+    case CVersion::C11:
+      return s + " 201112L";
+  }
+
+  return std::string();
+}
+
+std::string ToMacro(CxxVersion version) {
+  const std::string name = "__cplusplus";
+
+  switch (version) {
+    case CxxVersion::CXX98:
+    case CxxVersion::CXX03:
+      return name + " 199711L";
+    case CxxVersion::CXX11:
+      return name + " 201103L";
+    case CxxVersion::CXX14:
+      return name + " 201402L";
+    case CxxVersion::CXX17:
+      return name + " 201703L";
+  }
+
+  return std::string();
+}
+
+const std::map<std::string, CVersion> kFlagToCVersion{
+    {"-std=gnu99", CVersion::C99},
+    {"-std=c99", CVersion::C99},
+    {"-std=gnu11", CVersion::C11},
+    {"-std=c11", CVersion::C11}};
+
+const std::map<std::string, CxxVersion> kFlagToCxxVersion{
+    {"-std=gnu++11", CxxVersion::CXX11}, {"-std=c++11", CxxVersion::CXX11},
+    {"-std=gnu++98", CxxVersion::CXX98}, {"-std=c++98", CxxVersion::CXX98},
+    {"-std=gnu++03", CxxVersion::CXX03}, {"-std=c++03", CxxVersion::CXX03},
+    {"-std=gnu++14", CxxVersion::CXX14}, {"-std=c++14", CxxVersion::CXX14},
+    {"-std=c++1y", CxxVersion::CXX14},   {"-std=gnu++17", CxxVersion::CXX17},
+    {"-std=c++17", CxxVersion::CXX17},   {"-std=c++1z", CxxVersion::CXX17},
+};
+
+template <typename Enum>
+struct CompVersion {
+  bool operator()(Enum a, Enum b) {
+    return static_cast<int>(a) < static_cast<int>(b);
+  }
+};
+
+struct CompilerOptions {
+  std::optional<CVersion> c_version_;
+  std::optional<CxxVersion> cxx_version_;
+
+  void SetCVersion(CVersion ver) { SetVersionImpl(c_version_, ver); }
+
+  void SetCxxVersion(CxxVersion ver) { SetVersionImpl(cxx_version_, ver); }
+
+ private:
+  template <typename Version>
+  void SetVersionImpl(std::optional<Version>& cur_ver, Version ver) {
+    if (cur_ver)
+      cur_ver = std::max(*cur_ver, ver, CompVersion<Version>{});
+    else
+      cur_ver = ver;
+  }
+};
+
+void ParseCompilerOption(const std::string& flag, CompilerOptions* options) {
+  auto c_ver = kFlagToCVersion.find(flag);
+  if (c_ver != kFlagToCVersion.end())
+    options->SetCVersion(c_ver->second);
+
+  auto cxx_ver = kFlagToCxxVersion.find(flag);
+  if (cxx_ver != kFlagToCxxVersion.end())
+    options->SetCxxVersion(cxx_ver->second);
+}
+
+void ParseCompilerOptions(const std::vector<std::string>& cflags,
+                          CompilerOptions* options) {
+  for (const std::string& flag : cflags)
+    ParseCompilerOption(flag, options);
+}
+
+}  // namespace QtCreatorWriterUtils
+
+void QtCreatorWriter::HandleTarget(const Target* target) {
+  using namespace QtCreatorWriterUtils;
+
+  SourceFile build_file = builder_.loader()->BuildFileForLabel(target->label());
+  sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(build_file)));
+  AddToSources(target->settings()->import_manager().GetImportedFiles());
+
+  AddToSources(target->sources());
+  AddToSources(target->public_headers());
+
+  for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+    for (const auto& input : it.cur().inputs())
+      sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(input)));
+
+    SourceFile precompiled_source = it.cur().precompiled_source();
+    if (!precompiled_source.is_null()) {
+      sources_.insert(
+          FilePathToUTF8(build_settings_->GetFullPath(precompiled_source)));
+    }
+
+    for (const SourceDir& include_dir : it.cur().include_dirs()) {
+      includes_.insert(
+          FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
+    }
+
+    static constexpr const char* define_str = "#define ";
+    for (std::string define : it.cur().defines()) {
+      size_t equal_pos = define.find('=');
+      if (equal_pos != std::string::npos)
+        define[equal_pos] = ' ';
+      define.insert(0, define_str);
+      defines_.insert(define);
+    }
+
+    CompilerOptions options;
+    ParseCompilerOptions(it.cur().cflags(), &options);
+    ParseCompilerOptions(it.cur().cflags_c(), &options);
+    ParseCompilerOptions(it.cur().cflags_cc(), &options);
+
+    auto add_define_version = [this](auto& ver) {
+      if (ver)
+        defines_.insert(define_str + ToMacro(*ver));
+    };
+    add_define_version(options.c_version_);
+    add_define_version(options.cxx_version_);
+  }
+}
+
+void QtCreatorWriter::GenerateFile(const base::FilePath::CharType* suffix,
+                                   const std::set<std::string>& items) {
+  const base::FilePath file_path = project_prefix_.AddExtension(suffix);
+  std::ostringstream output;
+  for (const std::string& item : items)
+    output << item << std::endl;
+  WriteFileIfChanged(file_path, output.str(), &err_);
+}
+
+void QtCreatorWriter::Run() {
+  if (!DiscoverTargets())
+    return;
+
+  for (const Target* target : targets_) {
+    if (target->toolchain()->label() !=
+        builder_.loader()->GetDefaultToolchain())
+      continue;
+    HandleTarget(target);
+  }
+
+  std::set<std::string> empty_list;
+
+  GenerateFile(kMainProjectFileSuffix, empty_list);
+  GenerateFile(kSourcesFileSuffix, sources_);
+  GenerateFile(kIncludesFileSuffix, includes_);
+  GenerateFile(kDefinesFileSuffix, defines_);
+}
diff --git a/src/gn/qt_creator_writer.h b/src/gn/qt_creator_writer.h
new file mode 100644 (file)
index 0000000..c85a322
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_QT_CREATOR_WRITER_H_
+#define TOOLS_GN_QT_CREATOR_WRITER_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gn/err.h"
+#include "gn/target.h"
+
+class Builder;
+class BuildSettings;
+
+class QtCreatorWriter {
+ public:
+  static bool RunAndWriteFile(const BuildSettings* build_settings,
+                              const Builder& builder,
+                              Err* err,
+                              const std::string& root_target);
+
+ private:
+  QtCreatorWriter(const BuildSettings* build_settings,
+                  const Builder& builder,
+                  const base::FilePath& project_prefix,
+                  const std::string& root_target_name);
+  ~QtCreatorWriter();
+
+  void Run();
+
+  bool DiscoverTargets();
+  void HandleTarget(const Target* target);
+
+  void CollectDeps(const Target* target);
+  void AddToSources(const Target::FileList& files);
+  void GenerateFile(const base::FilePath::CharType* suffix,
+                    const std::set<std::string>& items);
+
+  const BuildSettings* build_settings_;
+  const Builder& builder_;
+  base::FilePath project_prefix_;
+  std::string root_target_name_;
+  std::set<const Target*> targets_;
+  std::set<std::string> sources_;
+  std::set<std::string> includes_;
+  std::set<std::string> defines_;
+  Err err_;
+
+  DISALLOW_COPY_AND_ASSIGN(QtCreatorWriter);
+};
+
+#endif  // TOOLS_GN_QT_CREATOR_WRITER_H_
diff --git a/src/gn/runtime_deps.cc b/src/gn/runtime_deps.cc
new file mode 100644 (file)
index 0000000..3b6d683
--- /dev/null
@@ -0,0 +1,319 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/runtime_deps.h"
+
+#include <map>
+#include <set>
+#include <sstream>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_split.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/loader.h"
+#include "gn/output_file.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/switches.h"
+#include "gn/target.h"
+#include "gn/trace.h"
+
+namespace {
+
+using RuntimeDepsVector = std::vector<std::pair<OutputFile, const Target*>>;
+
+// Adds the given file to the deps list if it hasn't already been listed in
+// the found_files list. Updates the list.
+void AddIfNew(const OutputFile& output_file,
+              const Target* source,
+              RuntimeDepsVector* deps,
+              std::set<OutputFile>* found_file) {
+  if (found_file->find(output_file) != found_file->end())
+    return;  // Already there.
+  deps->push_back(std::make_pair(output_file, source));
+}
+
+// Automatically converts a string that looks like a source to an OutputFile.
+void AddIfNew(const std::string& str,
+              const Target* source,
+              RuntimeDepsVector* deps,
+              std::set<OutputFile>* found_file) {
+  OutputFile output_file(
+      RebasePath(str, source->settings()->build_settings()->build_dir(),
+                 source->settings()->build_settings()->root_path_utf8()));
+  AddIfNew(output_file, source, deps, found_file);
+}
+
+// To avoid duplicate traversals of targets, or duplicating output files that
+// might be listed by more than one target, the set of targets and output files
+// that have been found so far is passed. The "value" of the seen_targets map
+// is a boolean indicating if the seen dep was a data dep (true = data_dep).
+// data deps add more stuff, so we will want to revisit a target if it's a
+// data dependency and we've previously only seen it as a regular dep.
+void RecursiveCollectRuntimeDeps(const Target* target,
+                                 bool is_target_data_dep,
+                                 RuntimeDepsVector* deps,
+                                 std::map<const Target*, bool>* seen_targets,
+                                 std::set<OutputFile>* found_files) {
+  const auto& found_seen_target = seen_targets->find(target);
+  if (found_seen_target != seen_targets->end()) {
+    // Already visited.
+    if (found_seen_target->second || !is_target_data_dep) {
+      // Already visited as a data dep, or the current dep is not a data
+      // dep so visiting again will be a no-op.
+      return;
+    }
+    // In the else case, the previously seen target was a regular dependency
+    // and we'll now process it as a data dependency.
+  }
+  (*seen_targets)[target] = is_target_data_dep;
+
+  // Add the main output file for executables, shared libraries, and
+  // loadable modules.
+  if (target->output_type() == Target::EXECUTABLE ||
+      target->output_type() == Target::LOADABLE_MODULE ||
+      target->output_type() == Target::SHARED_LIBRARY) {
+    for (const auto& runtime_output : target->runtime_outputs())
+      AddIfNew(runtime_output, target, deps, found_files);
+  }
+
+  // Add all data files.
+  for (const auto& file : target->data())
+    AddIfNew(file, target, deps, found_files);
+
+  // Actions/copy have all outputs considered when the're a data dep.
+  if (is_target_data_dep && (target->output_type() == Target::ACTION ||
+                             target->output_type() == Target::ACTION_FOREACH ||
+                             target->output_type() == Target::COPY_FILES)) {
+    std::vector<SourceFile> outputs;
+    target->action_values().GetOutputsAsSourceFiles(target, &outputs);
+    for (const auto& output_file : outputs)
+      AddIfNew(output_file.value(), target, deps, found_files);
+  }
+
+  // Data dependencies.
+  for (const auto& dep_pair : target->data_deps()) {
+    RecursiveCollectRuntimeDeps(dep_pair.ptr, true, deps, seen_targets,
+                                found_files);
+  }
+
+  // Do not recurse into bundle targets. A bundle's dependencies should be
+  // copied into the bundle itself for run-time access.
+  if (target->output_type() == Target::CREATE_BUNDLE) {
+    SourceDir bundle_root_dir =
+        target->bundle_data().GetBundleRootDirOutputAsDir(target->settings());
+    AddIfNew(bundle_root_dir.value(), target, deps, found_files);
+    return;
+  }
+
+  // Non-data dependencies (both public and private).
+  for (const auto& dep_pair : target->GetDeps(Target::DEPS_LINKED)) {
+    if (dep_pair.ptr->output_type() == Target::EXECUTABLE)
+      continue;  // Skip executables that aren't data deps.
+    if (dep_pair.ptr->output_type() == Target::SHARED_LIBRARY &&
+        (target->output_type() == Target::ACTION ||
+         target->output_type() == Target::ACTION_FOREACH)) {
+      // Skip shared libraries that action depends on,
+      // unless it were listed in data deps.
+      continue;
+    }
+    RecursiveCollectRuntimeDeps(dep_pair.ptr, false, deps, seen_targets,
+                                found_files);
+  }
+}
+
+bool CollectRuntimeDepsFromFlag(const BuildSettings* build_settings,
+                                const Builder& builder,
+                                RuntimeDepsVector* files_to_write,
+                                Err* err) {
+  std::string deps_target_list_file =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kRuntimeDepsListFile);
+
+  if (deps_target_list_file.empty())
+    return true;
+
+  std::string list_contents;
+  ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, deps_target_list_file);
+  if (!base::ReadFileToString(UTF8ToFilePath(deps_target_list_file),
+                              &list_contents)) {
+    *err = Err(Location(),
+               std::string("File for --") + switches::kRuntimeDepsListFile +
+                   " doesn't exist.",
+               "The file given was \"" + deps_target_list_file + "\"");
+    return false;
+  }
+  load_trace.Done();
+
+  SourceDir root_dir("//");
+  Label default_toolchain_label = builder.loader()->GetDefaultToolchain();
+  for (const auto& line : base::SplitString(
+           list_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    if (line.empty())
+      continue;
+    Label label =
+        Label::Resolve(root_dir, build_settings->root_path_utf8(),
+                       default_toolchain_label, Value(nullptr, line), err);
+    if (err->has_error())
+      return false;
+
+    const Item* item = builder.GetItem(label);
+    const Target* target = item ? item->AsTarget() : nullptr;
+    if (!target) {
+      *err =
+          Err(Location(),
+              "The label \"" + label.GetUserVisibleName(true) +
+                  "\" isn't a target.",
+              "When reading the line:\n  " + line +
+                  "\n"
+                  "from the --" +
+                  switches::kRuntimeDepsListFile + "=" + deps_target_list_file);
+      return false;
+    }
+
+    OutputFile output_file;
+    const char extension[] = ".runtime_deps";
+    if (target->output_type() == Target::SHARED_LIBRARY ||
+        target->output_type() == Target::LOADABLE_MODULE) {
+      // Force the first output for shared-library-type linker outputs since
+      // the dependency output files might not be the main output.
+      CHECK(!target->computed_outputs().empty());
+      output_file =
+          OutputFile(target->computed_outputs()[0].value() + extension);
+    } else {
+      output_file =
+          OutputFile(target->dependency_output_file().value() + extension);
+    }
+    files_to_write->push_back(std::make_pair(output_file, target));
+  }
+  return true;
+}
+
+bool WriteRuntimeDepsFile(const OutputFile& output_file,
+                          const Target* target,
+                          Err* err) {
+  SourceFile output_as_source =
+      output_file.AsSourceFile(target->settings()->build_settings());
+  base::FilePath data_deps_file =
+      target->settings()->build_settings()->GetFullPath(output_as_source);
+
+  std::stringstream contents;
+  for (const auto& pair : ComputeRuntimeDeps(target))
+    contents << pair.first.value() << std::endl;
+
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, output_as_source.value());
+  return WriteFileIfChanged(data_deps_file, contents.str(), err);
+}
+
+}  // namespace
+
+const char kRuntimeDeps_Help[] =
+    R"(Runtime dependencies
+
+  Runtime dependencies of a target are exposed via the "runtime_deps" category
+  of "gn desc" (see "gn help desc") or they can be written at build generation
+  time via write_runtime_deps(), or --runtime-deps-list-file (see "gn help
+  --runtime-deps-list-file").
+
+  To a first approximation, the runtime dependencies of a target are the set of
+  "data" files, data directories, and the shared libraries from all transitive
+  dependencies. Executables, shared libraries, and loadable modules are
+  considered runtime dependencies of themselves.
+
+Executables
+
+  Executable targets and those executable targets' transitive dependencies are
+  not considered unless that executable is listed in "data_deps". Otherwise, GN
+  assumes that the executable (and everything it requires) is a build-time
+  dependency only.
+
+Actions and copies
+
+  Action and copy targets that are listed as "data_deps" will have all of their
+  outputs and data files considered as runtime dependencies. Action and copy
+  targets that are "deps" or "public_deps" will have only their data files
+  considered as runtime dependencies. These targets can list an output file in
+  both the "outputs" and "data" lists to force an output file as a runtime
+  dependency in all cases.
+
+  The different rules for deps and data_deps are to express build-time (deps)
+  vs. run-time (data_deps) outputs. If GN counted all build-time copy steps as
+  data dependencies, there would be a lot of extra stuff, and if GN counted all
+  run-time dependencies as regular deps, the build's parallelism would be
+  unnecessarily constrained.
+
+  This rule can sometimes lead to unintuitive results. For example, given the
+  three targets:
+    A  --[data_deps]-->  B  --[deps]-->  ACTION
+  GN would say that A does not have runtime deps on the result of the ACTION,
+  which is often correct. But the purpose of the B target might be to collect
+  many actions into one logic unit, and the "data"-ness of A's dependency is
+  lost. Solutions:
+
+   - List the outputs of the action in its data section (if the results of
+     that action are always runtime files).
+   - Have B list the action in data_deps (if the outputs of the actions are
+     always runtime files).
+   - Have B list the action in both deps and data deps (if the outputs might be
+     used in both contexts and you don't care about unnecessary entries in the
+     list of files required at runtime).
+   - Split B into run-time and build-time versions with the appropriate "deps"
+     for each.
+
+Static libraries and source sets
+
+  The results of static_library or source_set targets are not considered
+  runtime dependencies since these are assumed to be intermediate targets only.
+  If you need to list a static library as a runtime dependency, you can
+  manually compute the .a/.lib file name for the current platform and list it
+  in the "data" list of a target (possibly on the static library target
+  itself).
+
+Multiple outputs
+
+  Linker tools can specify which of their outputs should be considered when
+  computing the runtime deps by setting runtime_outputs. If this is unset on
+  the tool, the default will be the first output only.
+)";
+
+RuntimeDepsVector ComputeRuntimeDeps(const Target* target) {
+  RuntimeDepsVector result;
+  std::map<const Target*, bool> seen_targets;
+  std::set<OutputFile> found_files;
+
+  // The initial target is not considered a data dependency so that actions's
+  // outputs (if the current target is an action) are not automatically
+  // considered data deps.
+  RecursiveCollectRuntimeDeps(target, false, &result, &seen_targets,
+                              &found_files);
+  return result;
+}
+
+bool WriteRuntimeDepsFilesIfNecessary(const BuildSettings* build_settings,
+                                      const Builder& builder,
+                                      Err* err) {
+  RuntimeDepsVector files_to_write;
+  if (!CollectRuntimeDepsFromFlag(build_settings, builder, &files_to_write,
+                                  err))
+    return false;
+
+  // Files scheduled by write_runtime_deps.
+  for (const Target* target : g_scheduler->GetWriteRuntimeDepsTargets()) {
+    files_to_write.push_back(
+        std::make_pair(target->write_runtime_deps_output(), target));
+  }
+
+  for (const auto& entry : files_to_write) {
+    // Currently this writes all runtime deps files sequentially. We generally
+    // expect few of these. We can run this on the worker pool if it looks
+    // like it's talking a long time.
+    if (!WriteRuntimeDepsFile(entry.first, entry.second, err))
+      return false;
+  }
+  return true;
+}
diff --git a/src/gn/runtime_deps.h b/src/gn/runtime_deps.h
new file mode 100644 (file)
index 0000000..82e0f10
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUNTIME_DEPS_H
+#define TOOLS_GN_RUNTIME_DEPS_H
+
+#include <utility>
+#include <vector>
+
+class Builder;
+class BuildSettings;
+class Err;
+class OutputFile;
+class Target;
+
+extern const char kRuntimeDeps_Help[];
+
+// Computes the runtime dependencies of the given target. The result is a list
+// of pairs listing the runtime dependency and the target that the runtime
+// dependency is from (for blaming).
+std::vector<std::pair<OutputFile, const Target*>> ComputeRuntimeDeps(
+    const Target* target);
+
+// Writes all runtime deps files requested on the command line, or does nothing
+// if no files were specified.
+bool WriteRuntimeDepsFilesIfNecessary(const BuildSettings* build_settings,
+                                      const Builder& builder,
+                                      Err* err);
+
+#endif  // TOOLS_GN_RUNTIME_DEPS_H
diff --git a/src/gn/runtime_deps_unittest.cc b/src/gn/runtime_deps_unittest.cc
new file mode 100644 (file)
index 0000000..baa14e4
--- /dev/null
@@ -0,0 +1,448 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/stl_util.h"
+#include "gn/runtime_deps.h"
+#include "gn/scheduler.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+void InitTargetWithType(TestWithScope& setup,
+                        Target* target,
+                        Target::OutputType type) {
+  target->set_output_type(type);
+  target->visibility().SetPublic();
+  target->SetToolchain(setup.toolchain());
+}
+
+// Convenience function to make the correct kind of pair.
+std::pair<OutputFile, const Target*> MakePair(const char* str,
+                                              const Target* t) {
+  return std::pair<OutputFile, const Target*>(OutputFile(str), t);
+}
+
+std::string GetVectorDescription(
+    const std::vector<std::pair<OutputFile, const Target*>>& v) {
+  std::string result;
+  for (size_t i = 0; i < v.size(); i++) {
+    if (i != 0)
+      result.append(", ");
+    result.append("\"" + v[i].first.value() + "\"");
+  }
+  return result;
+}
+
+}  // namespace
+
+using RuntimeDeps = TestWithScheduler;
+
+// Tests an exe depending on different types of libraries.
+TEST_F(RuntimeDeps, Libs) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy: main(exe) -> static library
+  //                                 -> shared library
+  //                                 -> loadable module
+  //                                 -> source set
+
+  Target stat(setup.settings(), Label(SourceDir("//"), "stat"));
+  InitTargetWithType(setup, &stat, Target::STATIC_LIBRARY);
+  stat.data().push_back("//stat.dat");
+  ASSERT_TRUE(stat.OnResolved(&err));
+
+  Target shared(setup.settings(), Label(SourceDir("//"), "shared"));
+  InitTargetWithType(setup, &shared, Target::SHARED_LIBRARY);
+  shared.data().push_back("//shared.dat");
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  Target loadable(setup.settings(), Label(SourceDir("//"), "loadable"));
+  InitTargetWithType(setup, &loadable, Target::LOADABLE_MODULE);
+  loadable.data().push_back("//loadable.dat");
+  ASSERT_TRUE(loadable.OnResolved(&err));
+
+  Target set(setup.settings(), Label(SourceDir("//"), "set"));
+  InitTargetWithType(setup, &set, Target::SOURCE_SET);
+  set.data().push_back("//set.dat");
+  ASSERT_TRUE(set.OnResolved(&err));
+
+  Target main(setup.settings(), Label(SourceDir("//"), "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.private_deps().push_back(LabelTargetPair(&stat));
+  main.private_deps().push_back(LabelTargetPair(&shared));
+  main.private_deps().push_back(LabelTargetPair(&loadable));
+  main.private_deps().push_back(LabelTargetPair(&set));
+  main.data().push_back("//main.dat");
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main, all 5 dat files, libshared.so, and
+  // libloadable.so.
+  ASSERT_EQ(8u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_TRUE(MakePair("./main", &main) == result[0]);
+
+  // The rest of the ordering is undefined. First the data files.
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../stat.dat", &stat)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../shared.dat", &shared)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../loadable.dat", &loadable)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../set.dat", &set)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../main.dat", &main)))
+      << GetVectorDescription(result);
+
+  // Check the static library and loadable module.
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("./libshared.so", &shared)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("./libloadable.so", &loadable)))
+      << GetVectorDescription(result);
+}
+
+// Tests that executables that aren't listed as data deps aren't included in
+// the output, but executables that are data deps are included.
+TEST_F(RuntimeDeps, ExeDataDep) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy: main(exe) -> datadep(exe) -> final_in(source set)
+  //                                 -> dep(exe) -> final_out(source set)
+  // The final_in/out targets each have data files. final_in's should be
+  // included, final_out's should not be.
+
+  Target final_in(setup.settings(), Label(SourceDir("//"), "final_in"));
+  InitTargetWithType(setup, &final_in, Target::SOURCE_SET);
+  final_in.data().push_back("//final_in.dat");
+  ASSERT_TRUE(final_in.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//"), "datadep"));
+  InitTargetWithType(setup, &datadep, Target::EXECUTABLE);
+  datadep.private_deps().push_back(LabelTargetPair(&final_in));
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  Target final_out(setup.settings(), Label(SourceDir("//"), "final_out"));
+  InitTargetWithType(setup, &final_out, Target::SOURCE_SET);
+  final_out.data().push_back("//final_out.dat");
+  ASSERT_TRUE(final_out.OnResolved(&err));
+
+  Target dep(setup.settings(), Label(SourceDir("//"), "dep"));
+  InitTargetWithType(setup, &dep, Target::EXECUTABLE);
+  dep.private_deps().push_back(LabelTargetPair(&final_out));
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target main(setup.settings(), Label(SourceDir("//"), "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.private_deps().push_back(LabelTargetPair(&dep));
+  main.data_deps().push_back(LabelTargetPair(&datadep));
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main, datadep, final_in.dat
+  ASSERT_EQ(3u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_TRUE(MakePair("./main", &main) == result[0]);
+
+  // The rest of the ordering is undefined.
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("./datadep", &datadep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../final_in.dat", &final_in)))
+      << GetVectorDescription(result);
+}
+
+TEST_F(RuntimeDeps, ActionSharedLib) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy: main(exe) -> action -> datadep(shared library)
+  //                                           -> dep(shared library)
+  // Datadep should be included, dep should not be.
+
+  Target dep(setup.settings(), Label(SourceDir("//"), "dep"));
+  InitTargetWithType(setup, &dep, Target::SHARED_LIBRARY);
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//"), "datadep"));
+  InitTargetWithType(setup, &datadep, Target::SHARED_LIBRARY);
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  Target action(setup.settings(), Label(SourceDir("//"), "action"));
+  InitTargetWithType(setup, &action, Target::ACTION);
+  action.private_deps().push_back(LabelTargetPair(&dep));
+  action.data_deps().push_back(LabelTargetPair(&datadep));
+  action.action_values().outputs() =
+      SubstitutionList::MakeForTest("//action.output");
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  Target main(setup.settings(), Label(SourceDir("//"), "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.private_deps().push_back(LabelTargetPair(&action));
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main and data_dep.
+  ASSERT_EQ(2u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_TRUE(MakePair("./main", &main) == result[0]);
+  EXPECT_TRUE(MakePair("./libdatadep.so", &datadep) == result[1]);
+}
+
+// Tests that action and copy outputs are considered if they're data deps, but
+// not if they're regular deps. Action and copy "data" files are always
+// included.
+TEST_F(RuntimeDeps, ActionOutputs) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy: main(exe) -> datadep (action)
+  //                                 -> datadep_copy (copy)
+  //                                 -> dep (action)
+  //                                 -> dep_copy (copy)
+
+  Target datadep(setup.settings(), Label(SourceDir("//"), "datadep"));
+  InitTargetWithType(setup, &datadep, Target::ACTION);
+  datadep.data().push_back("//datadep.data");
+  datadep.action_values().outputs() =
+      SubstitutionList::MakeForTest("//datadep.output");
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  Target datadep_copy(setup.settings(), Label(SourceDir("//"), "datadep_copy"));
+  InitTargetWithType(setup, &datadep_copy, Target::COPY_FILES);
+  datadep_copy.sources().push_back(SourceFile("//input"));
+  datadep_copy.data().push_back("//datadep_copy.data");
+  datadep_copy.action_values().outputs() =
+      SubstitutionList::MakeForTest("//datadep_copy.output");
+  ASSERT_TRUE(datadep_copy.OnResolved(&err));
+
+  Target dep(setup.settings(), Label(SourceDir("//"), "dep"));
+  InitTargetWithType(setup, &dep, Target::ACTION);
+  dep.data().push_back("//dep.data");
+  dep.action_values().outputs() = SubstitutionList::MakeForTest("//dep.output");
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target dep_copy(setup.settings(), Label(SourceDir("//"), "dep_copy"));
+  InitTargetWithType(setup, &dep_copy, Target::COPY_FILES);
+  dep_copy.sources().push_back(SourceFile("//input"));
+  dep_copy.data().push_back("//dep_copy/data/");  // Tests a directory.
+  dep_copy.action_values().outputs() =
+      SubstitutionList::MakeForTest("//dep_copy.output");
+  ASSERT_TRUE(dep_copy.OnResolved(&err));
+
+  Target main(setup.settings(), Label(SourceDir("//"), "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.private_deps().push_back(LabelTargetPair(&dep));
+  main.private_deps().push_back(LabelTargetPair(&dep_copy));
+  main.data_deps().push_back(LabelTargetPair(&datadep));
+  main.data_deps().push_back(LabelTargetPair(&datadep_copy));
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main, both datadeps files, but only
+  // the data file from dep.
+  ASSERT_EQ(7u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_TRUE(MakePair("./main", &main) == result[0]);
+
+  // The rest of the ordering is undefined.
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../datadep.data", &datadep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(
+      result, MakePair("../../datadep_copy.data", &datadep_copy)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../datadep.output", &datadep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(
+      result, MakePair("../../datadep_copy.output", &datadep_copy)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../dep.data", &dep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../dep_copy/data/", &dep_copy)))
+      << GetVectorDescription(result);
+
+  // Explicitly asking for the runtime deps of an action target only includes
+  // the data and not all outputs.
+  result = ComputeRuntimeDeps(&dep);
+  ASSERT_EQ(1u, result.size());
+  EXPECT_TRUE(MakePair("../../dep.data", &dep) == result[0]);
+}
+
+// Tests that the search for dependencies terminates at a bundle target,
+// ignoring any shared libraries or loadable modules that get copied into the
+// bundle.
+TEST_F(RuntimeDeps, CreateBundle) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy:
+  // main(exe) -> dep(bundle) -> dep(shared_library) -> dep(source set)
+  //                          -> dep(bundle_data) -> dep(loadable_module)
+  //                                                      -> data(lm.data)
+  //                          -> datadep(datadep) -> data(dd.data)
+
+  const SourceDir source_dir("//");
+  const std::string& build_dir = setup.build_settings()->build_dir().value();
+
+  Target loadable_module(setup.settings(),
+                         Label(source_dir, "loadable_module"));
+  InitTargetWithType(setup, &loadable_module, Target::LOADABLE_MODULE);
+  loadable_module.data().push_back("//lm.data");
+  ASSERT_TRUE(loadable_module.OnResolved(&err));
+
+  Target module_data(setup.settings(), Label(source_dir, "module_data"));
+  InitTargetWithType(setup, &module_data, Target::BUNDLE_DATA);
+  module_data.private_deps().push_back(LabelTargetPair(&loadable_module));
+  module_data.bundle_data().file_rules().push_back(BundleFileRule(
+      nullptr,
+      std::vector<SourceFile>{SourceFile(build_dir + "loadable_module.so")},
+      SubstitutionPattern::MakeForTest("{{bundle_resources_dir}}")));
+  ASSERT_TRUE(module_data.OnResolved(&err));
+
+  Target source_set(setup.settings(), Label(source_dir, "sources"));
+  InitTargetWithType(setup, &source_set, Target::SOURCE_SET);
+  source_set.sources().push_back(SourceFile(source_dir.value() + "foo.cc"));
+  ASSERT_TRUE(source_set.OnResolved(&err));
+
+  Target dylib(setup.settings(), Label(source_dir, "dylib"));
+  dylib.set_output_prefix_override(true);
+  dylib.set_output_extension("");
+  dylib.set_output_name("Bundle");
+  InitTargetWithType(setup, &dylib, Target::SHARED_LIBRARY);
+  dylib.private_deps().push_back(LabelTargetPair(&source_set));
+  ASSERT_TRUE(dylib.OnResolved(&err));
+
+  Target dylib_data(setup.settings(), Label(source_dir, "dylib_data"));
+  InitTargetWithType(setup, &dylib_data, Target::BUNDLE_DATA);
+  dylib_data.private_deps().push_back(LabelTargetPair(&dylib));
+  dylib_data.bundle_data().file_rules().push_back(BundleFileRule(
+      nullptr, std::vector<SourceFile>{SourceFile(build_dir + "dylib")},
+      SubstitutionPattern::MakeForTest("{{bundle_executable_dir}}")));
+  ASSERT_TRUE(dylib_data.OnResolved(&err));
+
+  Target data_dep(setup.settings(), Label(source_dir, "datadep"));
+  InitTargetWithType(setup, &data_dep, Target::EXECUTABLE);
+  data_dep.data().push_back("//dd.data");
+  ASSERT_TRUE(data_dep.OnResolved(&err));
+
+  Target bundle(setup.settings(), Label(source_dir, "bundle"));
+  InitTargetWithType(setup, &bundle, Target::CREATE_BUNDLE);
+  const std::string root_dir(build_dir + "Bundle.framework/");
+  const std::string contents_dir(root_dir + "Versions/A/");
+  bundle.bundle_data().root_dir() = SourceDir(root_dir);
+  bundle.bundle_data().contents_dir() = SourceDir(contents_dir);
+  bundle.bundle_data().resources_dir() = SourceDir(contents_dir + "Resources");
+  bundle.bundle_data().executable_dir() = SourceDir(contents_dir + "MacOS");
+  bundle.private_deps().push_back(LabelTargetPair(&dylib_data));
+  bundle.private_deps().push_back(LabelTargetPair(&module_data));
+  bundle.data_deps().push_back(LabelTargetPair(&data_dep));
+  bundle.data().push_back("//b.data");
+  ASSERT_TRUE(bundle.OnResolved(&err));
+
+  Target main(setup.settings(), Label(source_dir, "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.data_deps().push_back(LabelTargetPair(&bundle));
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main, datadep, final_in.dat
+  ASSERT_EQ(5u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_EQ(MakePair("./main", &main), result[0]);
+
+  // The rest of the ordering is undefined.
+
+  // The framework bundle's internal dependencies should not be included.
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("Bundle.framework/", &bundle)))
+      << GetVectorDescription(result);
+  // But direct data and data dependencies should be.
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("./datadep", &data_dep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../dd.data", &data_dep)))
+      << GetVectorDescription(result);
+  EXPECT_TRUE(base::ContainsValue(result, MakePair("../../b.data", &bundle)))
+      << GetVectorDescription(result);
+}
+
+// Tests that a dependency duplicated in regular and data deps is processed
+// as a data dep.
+TEST_F(RuntimeDeps, Dupe) {
+  TestWithScope setup;
+  Err err;
+
+  Target action(setup.settings(), Label(SourceDir("//"), "action"));
+  InitTargetWithType(setup, &action, Target::ACTION);
+  action.action_values().outputs() =
+      SubstitutionList::MakeForTest("//action.output");
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//"), "foo"));
+  InitTargetWithType(setup, &target, Target::EXECUTABLE);
+  target.private_deps().push_back(LabelTargetPair(&action));
+  target.data_deps().push_back(LabelTargetPair(&action));
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // The results should be the executable and the copy output.
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&target);
+  EXPECT_TRUE(
+      base::ContainsValue(result, MakePair("../../action.output", &action)))
+      << GetVectorDescription(result);
+}
+
+// Tests that actions can't have output substitutions.
+TEST_F(RuntimeDeps, WriteRuntimeDepsVariable) {
+  TestWithScope setup;
+  Err err;
+
+  // Should refuse to write files outside of the output dir.
+  EXPECT_FALSE(setup.ExecuteSnippet(
+      "group(\"foo\") { write_runtime_deps = \"//foo.txt\" }", &err));
+
+  // Should fail for garbage inputs.
+  err = Err();
+  EXPECT_FALSE(
+      setup.ExecuteSnippet("group(\"foo\") { write_runtime_deps = 0 }", &err));
+
+  // Should be able to write inside the out dir, and shouldn't write the one
+  // in the else clause.
+  err = Err();
+  EXPECT_TRUE(setup.ExecuteSnippet(
+      "if (true) {\n"
+      "  group(\"foo\") { write_runtime_deps = \"//out/Debug/foo.txt\" }\n"
+      "} else {\n"
+      "  group(\"bar\") { write_runtime_deps = \"//out/Debug/bar.txt\" }\n"
+      "}",
+      &err));
+  EXPECT_EQ(1U, setup.items().size());
+  EXPECT_EQ(1U, scheduler().GetWriteRuntimeDepsTargets().size());
+}
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
new file mode 100644 (file)
index 0000000..117442f
--- /dev/null
@@ -0,0 +1,451 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_project_writer.h"
+
+#include <fstream>
+#include <optional>
+#include <sstream>
+#include <tuple>
+
+#include "base/json/string_escape.h"
+#include "gn/builder.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/rust_project_writer_helpers.h"
+#include "gn/rust_tool.h"
+#include "gn/source_file.h"
+#include "gn/string_output_buffer.h"
+#include "gn/tool.h"
+
+#if defined(OS_WINDOWS)
+#define NEWLINE "\r\n"
+#else
+#define NEWLINE "\n"
+#endif
+
+// Current structure of rust-project.json output file
+//
+// {
+//    "roots": [
+//      "some/source/root"  // each crate's source root
+//    ],
+//    "crates": [
+//        {
+//            "deps": [
+//                {
+//                    "crate": 1, // index into crate array
+//                    "name": "alloc" // extern name of dependency
+//                },
+//            ],
+//            "edition": "2018", // edition of crate
+//            "cfg": [
+//              "unix", // "atomic" value config options
+//              "rust_panic=\"abort\""", // key="value" config options
+//            ]
+//            "root_module": "absolute path to crate",
+//            "label": "//path/target:value", // GN target for the crate
+//            "target": "x86_64-unknown-linux" // optional rustc target
+//        },
+// }
+//
+
+bool RustProjectWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+                                         const Builder& builder,
+                                         const std::string& file_name,
+                                         bool quiet,
+                                         Err* err) {
+  SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
+      Value(nullptr, file_name), err);
+  if (output_file.is_null())
+    return false;
+
+  base::FilePath output_path = build_settings->GetFullPath(output_file);
+
+  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+
+  StringOutputBuffer out_buffer;
+  std::ostream out(&out_buffer);
+
+  RenderJSON(build_settings, all_targets, out);
+
+  if (out_buffer.ContentsEqual(output_path)) {
+    return true;
+  }
+
+  return out_buffer.WriteToFile(output_path, err);
+}
+
+// Map of Targets to their index in the crates list (for linking dependencies to
+// their indexes).
+using TargetIndexMap = std::unordered_map<const Target*, uint32_t>;
+
+// A collection of Targets.
+using TargetsVector = UniqueVector<const Target*>;
+
+// Get the Rust deps for a target, recursively expanding OutputType::GROUPS
+// that are present in the GN structure.  This will return a flattened list of
+// deps from the groups, but will not expand a Rust lib dependency to find any
+// transitive Rust dependencies.
+void GetRustDeps(const Target* target, TargetsVector* rust_deps) {
+  for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+    const Target* dep = pair.ptr;
+
+    if (dep->source_types_used().RustSourceUsed()) {
+      // Include any Rust dep.
+      rust_deps->push_back(dep);
+    } else if (dep->output_type() == Target::OutputType::GROUP) {
+      // Inspect (recursively) any group to see if it contains Rust deps.
+      GetRustDeps(dep, rust_deps);
+    }
+  }
+}
+TargetsVector GetRustDeps(const Target* target) {
+  TargetsVector deps;
+  GetRustDeps(target, &deps);
+  return deps;
+}
+
+std::vector<std::string> ExtractCompilerArgs(const Target* target) {
+  std::vector<std::string> args;
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    auto rustflags = iter.cur().rustflags();
+    for (auto flag_iter = rustflags.begin(); flag_iter != rustflags.end();
+         flag_iter++) {
+      args.push_back(*flag_iter);
+    }
+  }
+  return args;
+}
+
+std::optional<std::string> FindArgValue(const char* arg,
+                                        const std::vector<std::string>& args) {
+  for (auto current = args.begin(); current != args.end();) {
+    // capture the current value
+    auto previous = *current;
+    // and increment
+    current++;
+
+    // if the previous argument matches `arg`, and after the above increment the
+    // end hasn't been reached, this current argument is the desired value.
+    if (previous == arg && current != args.end()) {
+      return std::make_optional(*current);
+    }
+  }
+  return std::nullopt;
+}
+
+std::optional<std::string> FindArgValueAfterPrefix(
+    const std::string& prefix,
+    const std::vector<std::string>& args) {
+  for (auto arg : args) {
+    if (!arg.compare(0, prefix.size(), prefix)) {
+      auto value = arg.substr(prefix.size());
+      return std::make_optional(value);
+    }
+  }
+  return std::nullopt;
+}
+
+std::vector<std::string> FindAllArgValuesAfterPrefix(
+    const std::string& prefix,
+    const std::vector<std::string>& args) {
+  std::vector<std::string> values;
+  for (auto arg : args) {
+    if (!arg.compare(0, prefix.size(), prefix)) {
+      auto value = arg.substr(prefix.size());
+      values.push_back(value);
+    }
+  }
+  return values;
+}
+
+// TODO(bwb) Parse sysroot structure from toml files. This is fragile and
+// might break if upstream changes the dependency structure.
+const std::string_view sysroot_crates[] = {"std",
+                                           "core",
+                                           "alloc",
+                                           "panic_unwind",
+                                           "proc_macro",
+                                           "test",
+                                           "panic_abort",
+                                           "unwind"};
+
+// Multiple sysroot crates have dependenices on each other.  This provides a
+// mechanism for specifying that in an extendible manner.
+const std::unordered_map<std::string_view, std::vector<std::string_view>>
+    sysroot_deps_map = {{"alloc", {"core"}},
+                        {"std", {"alloc", "core", "panic_abort", "unwind"}}};
+
+// Add each of the crates a sysroot has, including their dependencies.
+void AddSysrootCrate(const BuildSettings* build_settings,
+                     const std::string_view crate,
+                     const std::string_view current_sysroot,
+                     SysrootCrateIndexMap& sysroot_crate_lookup,
+                     CrateList& crate_list) {
+  if (sysroot_crate_lookup.find(crate) != sysroot_crate_lookup.end()) {
+    // If this sysroot crate is already in the lookup, we don't add it again.
+    return;
+  }
+
+  // Add any crates that this sysroot crate depends on.
+  auto deps_lookup = sysroot_deps_map.find(crate);
+  if (deps_lookup != sysroot_deps_map.end()) {
+    auto deps = (*deps_lookup).second;
+    for (auto dep : deps) {
+      AddSysrootCrate(build_settings, dep, current_sysroot,
+                      sysroot_crate_lookup, crate_list);
+    }
+  }
+
+  size_t crate_index = crate_list.size();
+  sysroot_crate_lookup.insert(std::make_pair(crate, crate_index));
+
+  base::FilePath rebased_out_dir =
+      build_settings->GetFullPath(build_settings->build_dir());
+  auto crate_path =
+      FilePathToUTF8(rebased_out_dir) + std::string(current_sysroot) +
+      "/lib/rustlib/src/rust/library/" + std::string(crate) + "/src/lib.rs";
+
+  Crate sysroot_crate =
+      Crate(SourceFile(crate_path), crate_index, std::string(crate), "2018");
+
+  sysroot_crate.AddConfigItem("debug_assertions");
+
+  if (deps_lookup != sysroot_deps_map.end()) {
+    auto deps = (*deps_lookup).second;
+    for (auto dep : deps) {
+      auto idx = sysroot_crate_lookup[dep];
+      sysroot_crate.AddDependency(idx, std::string(dep));
+    }
+  }
+
+  crate_list.push_back(sysroot_crate);
+}
+
+// Add the given sysroot to the project, if it hasn't already been added.
+void AddSysroot(const BuildSettings* build_settings,
+                const std::string_view sysroot,
+                SysrootIndexMap& sysroot_lookup,
+                CrateList& crate_list) {
+  // If this sysroot is already in the lookup, we don't add it again.
+  if (sysroot_lookup.find(sysroot) != sysroot_lookup.end()) {
+    return;
+  }
+
+  // Otherwise, add all of its crates
+  for (auto crate : sysroot_crates) {
+    AddSysrootCrate(build_settings, crate, sysroot, sysroot_lookup[sysroot],
+                    crate_list);
+  }
+}
+
+void AddSysrootDependencyToCrate(Crate* crate,
+                                 const SysrootCrateIndexMap& sysroot,
+                                 const std::string_view crate_name) {
+  if (const auto crate_idx = sysroot.find(crate_name);
+      crate_idx != sysroot.end()) {
+    crate->AddDependency(crate_idx->second, std::string(crate_name));
+  }
+}
+
+void AddTarget(const BuildSettings* build_settings,
+               const Target* target,
+               TargetIndexMap& lookup,
+               SysrootIndexMap& sysroot_lookup,
+               CrateList& crate_list) {
+  if (lookup.find(target) != lookup.end()) {
+    // If target is already in the lookup, we don't add it again.
+    return;
+  }
+
+  auto compiler_args = ExtractCompilerArgs(target);
+  auto compiler_target = FindArgValue("--target", compiler_args);
+
+  // Check what sysroot this target needs.  Add it to the crate list if it
+  // hasn't already been added.
+  auto rust_tool =
+      target->toolchain()->GetToolForTargetFinalOutputAsRust(target);
+  auto current_sysroot = rust_tool->GetSysroot();
+  if (current_sysroot != "" && sysroot_lookup.count(current_sysroot) == 0) {
+    AddSysroot(build_settings, current_sysroot, sysroot_lookup, crate_list);
+  }
+
+  auto crate_deps = GetRustDeps(target);
+
+  // Add all dependencies of this crate, before this crate.
+  for (const auto& dep : crate_deps) {
+    AddTarget(build_settings, dep, lookup, sysroot_lookup, crate_list);
+  }
+
+  // The index of a crate is its position (0-based) in the list of crates.
+  size_t crate_id = crate_list.size();
+
+  // Add the target to the crate lookup.
+  lookup.insert(std::make_pair(target, crate_id));
+
+  SourceFile crate_root = target->rust_values().crate_root();
+  std::string crate_label = target->label().GetUserVisibleName(false);
+
+  auto edition =
+      FindArgValueAfterPrefix(std::string("--edition="), compiler_args);
+  if (!edition.has_value()) {
+    edition = FindArgValue("--edition", compiler_args);
+  }
+
+  Crate crate =
+      Crate(crate_root, crate_id, crate_label, edition.value_or("2015"));
+
+  crate.SetCompilerArgs(compiler_args);
+  if (compiler_target.has_value())
+    crate.SetCompilerTarget(compiler_target.value());
+
+  ConfigList cfgs =
+      FindAllArgValuesAfterPrefix(std::string("--cfg="), compiler_args);
+
+  crate.AddConfigItem("test");
+  crate.AddConfigItem("debug_assertions");
+
+  for (auto& cfg : cfgs) {
+    crate.AddConfigItem(cfg);
+  }
+
+  // Add the sysroot dependencies, if there is one.
+  if (current_sysroot != "") {
+    const auto& sysroot = sysroot_lookup[current_sysroot];
+    AddSysrootDependencyToCrate(&crate, sysroot, "core");
+    AddSysrootDependencyToCrate(&crate, sysroot, "alloc");
+    AddSysrootDependencyToCrate(&crate, sysroot, "std");
+
+    // Proc macros have the proc_macro crate as a direct dependency
+    if (std::string_view(rust_tool->name()) ==
+        std::string_view(RustTool::kRsToolMacro)) {
+      AddSysrootDependencyToCrate(&crate, sysroot, "proc_macro");
+    }
+  }
+
+  // Add the rest of the crate dependencies.
+  for (const auto& dep : crate_deps) {
+    auto idx = lookup[dep];
+    crate.AddDependency(idx, dep->rust_values().crate_name());
+  }
+
+  crate_list.push_back(crate);
+}
+
+void WriteCrates(const BuildSettings* build_settings,
+                 CrateList& crate_list,
+                 std::ostream& rust_project) {
+  // produce a de-duplicated set of source roots:
+  std::set<std::string> roots;
+  for (auto& crate : crate_list) {
+    roots.insert(
+        FilePathToUTF8(build_settings->GetFullPath(crate.root().GetDir())));
+  }
+
+  rust_project << "{" NEWLINE;
+  rust_project << "  \"roots\": [";
+  bool first_root = true;
+  for (auto& root : roots) {
+    if (!first_root)
+      rust_project << ",";
+    first_root = false;
+
+    rust_project << NEWLINE "    \"" << root << "\"";
+  }
+  rust_project << NEWLINE "  ]," NEWLINE;
+  rust_project << "  \"crates\": [";
+  bool first_crate = true;
+  for (auto& crate : crate_list) {
+    if (!first_crate)
+      rust_project << ",";
+    first_crate = false;
+
+    auto crate_module =
+        FilePathToUTF8(build_settings->GetFullPath(crate.root()));
+
+    rust_project << NEWLINE << "    {" NEWLINE
+                 << "      \"crate_id\": " << crate.index() << "," NEWLINE
+                 << "      \"root_module\": \"" << crate_module << "\"," NEWLINE
+                 << "      \"label\": \"" << crate.label() << "\"," NEWLINE;
+
+    auto compiler_target = crate.CompilerTarget();
+    if (compiler_target.has_value()) {
+      rust_project << "      \"target\": \"" << compiler_target.value()
+                   << "\"," NEWLINE;
+    }
+
+    auto compiler_args = crate.CompilerArgs();
+    if (!compiler_args.empty()) {
+      rust_project << "      \"compiler_args\": [";
+      bool first_arg = true;
+      for (auto& arg : crate.CompilerArgs()) {
+        if (!first_arg)
+          rust_project << ", ";
+        first_arg = false;
+
+        std::string escaped_arg;
+        base::EscapeJSONString(arg, false, &escaped_arg);
+
+        rust_project << "\"" << escaped_arg << "\"";
+      }
+      rust_project << "]," << NEWLINE;
+    }
+
+    rust_project << "      \"deps\": [";
+    bool first_dep = true;
+    for (auto& dep : crate.dependencies()) {
+      if (!first_dep)
+        rust_project << ",";
+      first_dep = false;
+
+      rust_project << NEWLINE << "        {" NEWLINE
+                   << "          \"crate\": " << dep.first << "," NEWLINE
+                   << "          \"name\": \"" << dep.second << "\"" NEWLINE
+                   << "        }";
+    }
+    rust_project << NEWLINE "      ]," NEWLINE;  // end dep list
+
+    rust_project << "      \"edition\": \"" << crate.edition() << "\"," NEWLINE;
+
+    rust_project << "      \"cfg\": [";
+    bool first_cfg = true;
+    for (const auto& cfg : crate.configs()) {
+      if (!first_cfg)
+        rust_project << ",";
+      first_cfg = false;
+
+      std::string escaped_config;
+      base::EscapeJSONString(cfg, false, &escaped_config);
+
+      rust_project << NEWLINE;
+      rust_project << "        \"" << escaped_config << "\"";
+    }
+    rust_project << NEWLINE;
+    rust_project << "      ]" NEWLINE;  // end cfgs
+
+    rust_project << "    }";  // end crate
+  }
+  rust_project << NEWLINE "  ]" NEWLINE;  // end crate list
+  rust_project << "}" NEWLINE;
+}
+
+void RustProjectWriter::RenderJSON(const BuildSettings* build_settings,
+                                   std::vector<const Target*>& all_targets,
+                                   std::ostream& rust_project) {
+  TargetIndexMap lookup;
+  SysrootIndexMap sysroot_lookup;
+  CrateList crate_list;
+
+  // All the crates defined in the project.
+  for (const auto* target : all_targets) {
+    if (!target->IsBinary() || !target->source_types_used().RustSourceUsed())
+      continue;
+
+    AddTarget(build_settings, target, lookup, sysroot_lookup, crate_list);
+  }
+
+  WriteCrates(build_settings, crate_list, rust_project);
+}
diff --git a/src/gn/rust_project_writer.h b/src/gn/rust_project_writer.h
new file mode 100644 (file)
index 0000000..5c5d453
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_PROJECT_WRITER_H_
+#define TOOLS_GN_RUST_PROJECT_WRITER_H_
+
+#include "gn/err.h"
+#include "gn/target.h"
+
+class Builder;
+class BuildSettings;
+
+// rust-project.json is an output format describing the rust build graph. It is
+// used by rust-analyzer (a LSP server), similar to compile-commands.json.
+//
+// an example output is in rust_project_writer.cc
+class RustProjectWriter {
+ public:
+  // Write Rust build graph into a json file located by parameter file_name.
+  //
+  // Parameter quiet is not used.
+  static bool RunAndWriteFiles(const BuildSettings* build_setting,
+                               const Builder& builder,
+                               const std::string& file_name,
+                               bool quiet,
+                               Err* err);
+  static void RenderJSON(const BuildSettings* build_settings,
+                         std::vector<const Target*>& all_targets,
+                         std::ostream& rust_project);
+
+ private:
+  // This function visits the deps graph of a target in a DFS fashion.
+  static void VisitDeps(const Target* target, std::set<const Target*>* visited);
+};
+
+#endif  // TOOLS_GN_RUST_PROJECT_WRITER_H_
diff --git a/src/gn/rust_project_writer_helpers.h b/src/gn/rust_project_writer_helpers.h
new file mode 100644 (file)
index 0000000..0020e2c
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
+#define TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
+
+#include <fstream>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#include "build_settings.h"
+#include "gn/source_file.h"
+#include "gn/target.h"
+
+// These are internal types and helper functions for RustProjectWriter that have
+// been extracted for easier testability.
+
+// Crate Index in the generated file
+using CrateIndex = size_t;
+
+using ConfigList = std::vector<std::string>;
+using Dependency = std::pair<CrateIndex, std::string>;
+using DependencyList = std::vector<Dependency>;
+
+// This class represents a crate to be serialized out as part of the
+// rust-project.json file.  This is used to separate the generating
+// of the data that needs to be in the file, from the file itself.
+class Crate {
+ public:
+  Crate(SourceFile root,
+        CrateIndex index,
+        std::string label,
+        std::string edition)
+      : root_(root), index_(index), label_(label), edition_(edition) {}
+
+  ~Crate() = default;
+
+  // Add a config item to the crate.
+  void AddConfigItem(std::string cfg_item) { configs_.push_back(cfg_item); }
+
+  // Add another crate as a dependency of this one.
+  void AddDependency(CrateIndex index, std::string name) {
+    deps_.push_back(std::make_pair(index, name));
+  }
+
+  // Set the compiler arguments used to invoke the compilation of this crate
+  void SetCompilerArgs(std::vector<std::string> args) { compiler_args_ = args; }
+
+  // Set the compiler target ("e.g. x86_64-linux-kernel")
+  void SetCompilerTarget(std::string target) { compiler_target_ = target; }
+
+  // Returns the root file for the crate.
+  SourceFile& root() { return root_; }
+
+  // Returns the crate index.
+  CrateIndex index() { return index_; };
+
+  // Returns the displayable crate label.
+  const std::string& label() { return label_; }
+
+  // Returns the Rust Edition this crate uses.
+  const std::string& edition() { return edition_; }
+
+  // Return the set of config items for this crate.
+  ConfigList& configs() { return configs_; }
+
+  // Return the set of dependencies for this crate.
+  DependencyList& dependencies() { return deps_; }
+
+  // Return the compiler arguments used to invoke the compilation of this crate
+  const std::vector<std::string>& CompilerArgs() { return compiler_args_; }
+
+  // Return the compiler target "triple" from the compiler args
+  const std::optional<std::string>& CompilerTarget() {
+    return compiler_target_;
+  }
+
+ private:
+  SourceFile root_;
+  CrateIndex index_;
+  std::string label_;
+  std::string edition_;
+  ConfigList configs_;
+  DependencyList deps_;
+  std::optional<std::string> compiler_target_;
+  std::vector<std::string> compiler_args_;
+};
+
+using CrateList = std::vector<Crate>;
+
+// Mapping of a sysroot crate (path) to it's index in the crates list.
+using SysrootCrateIndexMap = std::unordered_map<std::string_view, CrateIndex>;
+
+// Mapping of a sysroot (path) to the mapping of each of the sysroot crates to
+// their index in the crates list.
+using SysrootIndexMap =
+    std::unordered_map<std::string_view, SysrootCrateIndexMap>;
+
+// Add all of the crates for a sysroot (path) to the rust_project ostream.
+// Add the given sysroot to the project, if it hasn't already been added.
+void AddSysroot(const BuildSettings* build_settings,
+                const std::string_view sysroot,
+                SysrootIndexMap& sysroot_lookup,
+                CrateList& crate_list);
+
+// Write the entire rust-project.json file contents into the given stream, based
+// on the the given crates list.
+void WriteCrates(const BuildSettings* build_settings,
+                 CrateList& crate_list,
+                 std::ostream& rust_project);
+
+// Assemble the compiler arguments for the given GN Target.
+std::vector<std::string> ExtractCompilerArgs(const Target* target);
+
+// Find the value of an argument that's passed to the compiler as two
+// consecutive strings in the list of arguments:  ["arg", "value"]
+std::optional<std::string> FindArgValue(const char* arg,
+                                        const std::vector<std::string>& args);
+
+// Find the first argument that matches the prefix, returning the value after
+// the prefix.  e.g. ˝--arg=value", is returned as "value" if the prefix
+// "--arg=" is used.
+std::optional<std::string> FindArgValueAfterPrefix(
+    const std::string& prefix,
+    const std::vector<std::string>& args);
+
+// Find all arguments that match the given prefix, returning the value after
+// the prefix for each one.  e.g. "--cfg=value" is returned as "value" if the
+// prefix "--cfg=" is used.
+std::vector<std::string> FindAllArgValuesAfterPrefix(
+    const std::string& prefix,
+    const std::vector<std::string>& args);
+
+
+#endif  // TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
diff --git a/src/gn/rust_project_writer_helpers_unittest.cc b/src/gn/rust_project_writer_helpers_unittest.cc
new file mode 100644 (file)
index 0000000..7ff3b48
--- /dev/null
@@ -0,0 +1,359 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_project_writer_helpers.h"
+
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+#include "gn/string_output_buffer.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+static void ExpectEqOrShowDiff(const char* expected,
+                               const std::string& actual) {
+  if (expected != actual) {
+    printf("\nExpected: >>>\n%s<<<\n", expected);
+    printf("  Actual: >>>\n%s<<<\n", actual.c_str());
+  }
+  EXPECT_EQ(expected, actual);
+}
+
+using RustProjectWriterHelper = TestWithScheduler;
+
+TEST_F(RustProjectWriterHelper, WriteCrates) {
+  TestWithScope setup;
+
+  CrateList crates;
+  Crate dep =
+      Crate(SourceFile("/root/tortoise/lib.rs"), 0, "//tortoise:bar", "2015");
+  Crate target =
+      Crate(SourceFile("/root/hare/lib.rs"), 1, "//hare:bar", "2015");
+  target.AddDependency(0, "tortoise");
+  target.AddConfigItem("unix");
+  target.AddConfigItem("feature=\"test\"");
+
+  crates.push_back(dep);
+  crates.push_back(target);
+
+  std::ostringstream stream;
+  WriteCrates(setup.build_settings(), crates, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"/root/hare/\",\n"
+      "    \"/root/tortoise/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"/root/tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"/root/hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"unix\",\n"
+      "        \"feature=\\\"test\\\"\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectWriterHelper, SysrootDepsAreCorrect) {
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("/root"));
+
+  SysrootIndexMap sysroot_lookup;
+  CrateList crates;
+
+  AddSysroot(setup.build_settings(), "sysroot", sysroot_lookup, crates);
+
+  std::ostringstream stream;
+  WriteCrates(setup.build_settings(), crates, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/alloc/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/core/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/panic_abort/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/panic_unwind/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/proc_macro/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/std/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/test/src/\",\n"
+      "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/unwind/src/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/core/src/lib.rs\",\n"
+      "      \"label\": \"core\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/alloc/src/lib.rs\",\n"
+      "      \"label\": \"alloc\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"core\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 2,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/panic_abort/src/lib.rs\",\n"
+      "      \"label\": \"panic_abort\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 3,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/unwind/src/lib.rs\",\n"
+      "      \"label\": \"unwind\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 4,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/std/src/lib.rs\",\n"
+      "      \"label\": \"std\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 1,\n"
+      "          \"name\": \"alloc\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"core\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 2,\n"
+      "          \"name\": \"panic_abort\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 3,\n"
+      "          \"name\": \"unwind\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 5,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/panic_unwind/src/lib.rs\",\n"
+      "      \"label\": \"panic_unwind\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 6,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/proc_macro/src/lib.rs\",\n"
+      "      \"label\": \"proc_macro\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 7,\n"
+      "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/library/test/src/lib.rs\",\n"
+      "      \"label\": \"test\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractCompilerTargetTupleSimple) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--target");
+  target.config_values().rustflags().push_back("x86-someos");
+  target.config_values().rustflags().push_back("--edition=2018");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::optional<std::string> result = FindArgValue("--target", args);
+  auto expected = std::optional<std::string>{"x86-someos"};
+  EXPECT_EQ(expected, result);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractCompilerTargetTupleMissing) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back(
+      "--cfg=featur4e=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("x86-someos");
+  target.config_values().rustflags().push_back("--edition=2018");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::optional<std::string> result = FindArgValue("--target", args);
+  auto expected = std::nullopt;
+  EXPECT_EQ(expected, result);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractCompilerTargetTupleDontFallOffEnd) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.config_values().rustflags().push_back("--target");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::optional<std::string> result = FindArgValue("--target", args);
+  auto expected = std::nullopt;
+  EXPECT_EQ(expected, result);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractFirstArgValueWithPrefixMissing) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.config_values().rustflags().push_back("--target");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::optional<std::string> result =
+      FindArgValueAfterPrefix("--missing", args);
+  auto expected = std::nullopt;
+  EXPECT_EQ(expected, result);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractFirstArgValueWithPrefix) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.config_values().rustflags().push_back("--target");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::optional<std::string> result =
+      FindArgValueAfterPrefix("--edition=", args);
+  auto expected = std::optional<std::string>{"2018"};
+  EXPECT_EQ(expected, result);
+}
+
+TEST_F(RustProjectWriterHelper, ExtractAllArgValueWithPrefix) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.config_values().rustflags().push_back("--cfg=feature=\"bar_enabled\"");
+  target.config_values().rustflags().push_back("--target");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::vector<std::string> result = FindAllArgValuesAfterPrefix("--cfg=", args);
+  std::vector<std::string> expected = {"feature=\"foo_enabled\"",
+                                       "feature=\"bar_enabled\""};
+  EXPECT_EQ(expected, result);
+}
\ No newline at end of file
diff --git a/src/gn/rust_project_writer_unittest.cc b/src/gn/rust_project_writer_unittest.cc
new file mode 100644 (file)
index 0000000..675f9a6
--- /dev/null
@@ -0,0 +1,524 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_project_writer.h"
+#include "base/strings/string_util.h"
+#include "base/files/file_path.h"
+#include "gn/filesystem_utils.h"
+#include "gn/substitution_list.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+
+static void ExpectEqOrShowDiff(const char* expected, const std::string& actual) {
+  if(expected != actual) {
+    printf("\nExpected: >>>\n%s<<<\n", expected);
+    printf("  Actual: >>>\n%s<<<\n", actual.c_str());
+  }
+  EXPECT_EQ(expected, actual);
+}
+
+using RustProjectJSONWriter = TestWithScheduler;
+
+TEST_F(RustProjectJSONWriter, OneRustTarget) {
+  Err err;
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"path/foo/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"path/foo/lib.rs\",\n"
+      "      \"label\": \"//foo:bar\",\n"
+      "      \"compiler_args\": [\"--cfg=feature=\\\"foo_enabled\\\"\"],\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\",\n"
+      "        \"feature=\\\"foo_enabled\\\"\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, RustTargetDep) {
+  Err err;
+  TestWithScope setup;
+
+  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
+  dep.set_output_type(Target::RUST_LIBRARY);
+  dep.visibility().SetPublic();
+  SourceFile tlib("//tortoise/lib.rs");
+  dep.sources().push_back(tlib);
+  dep.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep.rust_values().set_crate_root(tlib);
+  dep.rust_values().crate_name() = "tortoise";
+  dep.SetToolchain(setup.toolchain());
+
+  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile harelib("//hare/lib.rs");
+  target.sources().push_back(harelib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(harelib);
+  target.rust_values().crate_name() = "hare";
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"hare/\",\n"
+      "    \"tortoise/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, RustTargetDepTwo) {
+  Err err;
+  TestWithScope setup;
+
+  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
+  dep.set_output_type(Target::RUST_LIBRARY);
+  dep.visibility().SetPublic();
+  SourceFile tlib("//tortoise/lib.rs");
+  dep.sources().push_back(tlib);
+  dep.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep.rust_values().set_crate_root(tlib);
+  dep.rust_values().crate_name() = "tortoise";
+  dep.SetToolchain(setup.toolchain());
+
+  Target dep2(setup.settings(), Label(SourceDir("//achilles/"), "bar"));
+  dep2.set_output_type(Target::RUST_LIBRARY);
+  dep2.visibility().SetPublic();
+  SourceFile alib("//achilles/lib.rs");
+  dep2.sources().push_back(alib);
+  dep2.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep2.rust_values().set_crate_root(alib);
+  dep2.rust_values().crate_name() = "achilles";
+  dep2.SetToolchain(setup.toolchain());
+
+  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile harelib("//hare/lib.rs");
+  target.sources().push_back(harelib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(harelib);
+  target.rust_values().crate_name() = "hare";
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"achilles/\",\n"
+      "    \"hare/\",\n"
+      "    \"tortoise/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"achilles/lib.rs\",\n"
+      "      \"label\": \"//achilles:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 2,\n"
+      "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 1,\n"
+      "          \"name\": \"achilles\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+// Test that when outputting dependencies, only Rust deps are returned,
+// and that any groups are inspected to see if they include Rust deps.
+TEST_F(RustProjectJSONWriter, RustTargetGetDepRustOnly) {
+  Err err;
+  TestWithScope setup;
+
+  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
+  dep.set_output_type(Target::RUST_LIBRARY);
+  dep.visibility().SetPublic();
+  SourceFile tlib("//tortoise/lib.rs");
+  dep.sources().push_back(tlib);
+  dep.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep.rust_values().set_crate_root(tlib);
+  dep.rust_values().crate_name() = "tortoise";
+  dep.SetToolchain(setup.toolchain());
+
+  Target dep2(setup.settings(), Label(SourceDir("//achilles/"), "bar"));
+  dep2.set_output_type(Target::STATIC_LIBRARY);
+  dep2.visibility().SetPublic();
+  SourceFile alib("//achilles/lib.o");
+  dep2.SetToolchain(setup.toolchain());
+
+  Target dep3(setup.settings(), Label(SourceDir("//achilles/"), "group"));
+  dep3.set_output_type(Target::GROUP);
+  dep3.visibility().SetPublic();
+  dep3.public_deps().push_back(LabelTargetPair(&dep));
+  dep3.SetToolchain(setup.toolchain());
+
+  Target dep4(setup.settings(), Label(SourceDir("//tortoise/"), "macro"));
+  dep4.set_output_type(Target::RUST_PROC_MACRO);
+  dep4.visibility().SetPublic();
+  SourceFile tmlib("//tortoise/macro/lib.rs");
+  dep4.sources().push_back(tmlib);
+  dep4.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep4.rust_values().set_crate_root(tmlib);
+  dep4.rust_values().crate_name() = "tortoise_macro";
+  dep4.SetToolchain(setup.toolchain());
+
+  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile harelib("//hare/lib.rs");
+  target.sources().push_back(harelib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(harelib);
+  target.rust_values().crate_name() = "hare";
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep3));
+  target.public_deps().push_back(LabelTargetPair(&dep4));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"hare/\",\n"
+      "    \"tortoise/\",\n"
+      "    \"tortoise/macro/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"tortoise/macro/lib.rs\",\n"
+      "      \"label\": \"//tortoise:macro\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 2,\n"
+      "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 1,\n"
+      "          \"name\": \"tortoise_macro\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, OneRustTargetWithRustcTargetSet) {
+  Err err;
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--target");
+  target.config_values().rustflags().push_back("x86-64_unknown");
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"path/foo/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"path/foo/lib.rs\",\n"
+      "      \"label\": \"//foo:bar\",\n"
+      "      \"target\": \"x86-64_unknown\",\n"
+      "      \"compiler_args\": [\"--target\", \"x86-64_unknown\"],\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, OneRustTargetWithEditionSet) {
+  Err err;
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"path/foo/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"path/foo/lib.rs\",\n"
+      "      \"label\": \"//foo:bar\",\n"
+      "      \"compiler_args\": [\"--edition=2018\"],\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, OneRustTargetWithEditionSetAlternate) {
+  Err err;
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--edition");
+  target.config_values().rustflags().push_back("2018");
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [\n"
+      "    \"path/foo/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"path/foo/lib.rs\",\n"
+      "      \"label\": \"//foo:bar\",\n"
+      "      \"compiler_args\": [\"--edition\", \"2018\"],\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2018\",\n"
+      "      \"cfg\": [\n"
+      "        \"test\",\n"
+      "        \"debug_assertions\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  ExpectEqOrShowDiff(expected_json, out);
+}
\ No newline at end of file
diff --git a/src/gn/rust_substitution_type.cc b/src/gn/rust_substitution_type.cc
new file mode 100644 (file)
index 0000000..c417019
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_substitution_type.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "gn/err.h"
+#include "gn/substitution_type.h"
+
+const SubstitutionTypes RustSubstitutions = {
+    &kRustSubstitutionCrateName,       &kRustSubstitutionCrateType,
+    &kRustSubstitutionRustDeps,        &kRustSubstitutionRustFlags,
+    &kRustSubstitutionRustEnv,         &kRustSubstitutionExterns,
+    &kRustSubstitutionSources,
+};
+
+// Valid for Rust tools.
+const Substitution kRustSubstitutionCrateName = {"{{crate_name}}",
+                                                 "crate_name"};
+const Substitution kRustSubstitutionCrateType = {"{{crate_type}}",
+                                                 "crate_type"};
+const Substitution kRustSubstitutionExterns = {"{{externs}}", "externs"};
+const Substitution kRustSubstitutionRustDeps = {"{{rustdeps}}", "rustdeps"};
+const Substitution kRustSubstitutionRustEnv = {"{{rustenv}}", "rustenv"};
+const Substitution kRustSubstitutionRustFlags = {"{{rustflags}}", "rustflags"};
+const Substitution kRustSubstitutionSources = {"{{sources}}", "sources"};
+
+bool IsValidRustSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
+         type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
+         type == &kRustSubstitutionCrateName ||
+         type == &kRustSubstitutionCrateType ||
+         type == &kRustSubstitutionExterns ||
+         type == &kRustSubstitutionRustDeps ||
+         type == &kRustSubstitutionRustEnv ||
+         type == &kRustSubstitutionRustFlags ||
+         type == &kRustSubstitutionSources;
+}
diff --git a/src/gn/rust_substitution_type.h b/src/gn/rust_substitution_type.h
new file mode 100644 (file)
index 0000000..2eeb7dc
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_SUBSTITUTION_TYPE_H_
+#define TOOLS_GN_RUST_SUBSTITUTION_TYPE_H_
+
+#include <set>
+#include <vector>
+
+#include "gn/substitution_type.h"
+
+// The set of substitutions available to Rust tools.
+extern const SubstitutionTypes RustSubstitutions;
+
+// Valid for Rust tools.
+extern const Substitution kRustSubstitutionCrateName;
+extern const Substitution kRustSubstitutionCrateType;
+extern const Substitution kRustSubstitutionExterns;
+extern const Substitution kRustSubstitutionRustDeps;
+extern const Substitution kRustSubstitutionRustEnv;
+extern const Substitution kRustSubstitutionRustFlags;
+extern const Substitution kRustSubstitutionSources;
+
+bool IsValidRustSubstitution(const Substitution* type);
+
+#endif  // TOOLS_GN_RUST_SUBSTITUTION_TYPE_H_
diff --git a/src/gn/rust_tool.cc b/src/gn/rust_tool.cc
new file mode 100644 (file)
index 0000000..0b8921f
--- /dev/null
@@ -0,0 +1,123 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_tool.h"
+
+#include "gn/rust_substitution_type.h"
+#include "gn/target.h"
+
+const char* RustTool::kRsToolBin = "rust_bin";
+const char* RustTool::kRsToolCDylib = "rust_cdylib";
+const char* RustTool::kRsToolDylib = "rust_dylib";
+const char* RustTool::kRsToolMacro = "rust_macro";
+const char* RustTool::kRsToolRlib = "rust_rlib";
+const char* RustTool::kRsToolStaticlib = "rust_staticlib";
+
+RustTool::RustTool(const char* n) : Tool(n) {
+  CHECK(ValidateName(n));
+  // TODO: should these be settable in toolchain definition?
+  set_framework_switch("-lframework=");
+  set_lib_dir_switch("-Lnative=");
+  set_lib_switch("-l");
+  set_linker_arg("-Clink-arg=");
+}
+
+RustTool::~RustTool() = default;
+
+RustTool* RustTool::AsRust() {
+  return this;
+}
+const RustTool* RustTool::AsRust() const {
+  return this;
+}
+
+bool RustTool::ValidateName(const char* name) const {
+  return name == kRsToolBin || name == kRsToolCDylib || name == kRsToolDylib ||
+         name == kRsToolMacro || name == kRsToolRlib ||
+         name == kRsToolStaticlib;
+}
+
+void RustTool::SetComplete() {
+  SetToolComplete();
+}
+
+std::string_view RustTool::GetSysroot() const {
+  return rust_sysroot_;
+}
+
+bool RustTool::SetOutputExtension(const Value* value,
+                                  std::string* var,
+                                  Err* err) {
+  DCHECK(!complete_);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::STRING, err))
+    return false;
+  if (value->string_value().empty())
+    return true;
+
+  *var = std::move(value->string_value());
+  return true;
+}
+
+bool RustTool::ReadOutputsPatternList(Scope* scope,
+                                      const char* var,
+                                      SubstitutionList* field,
+                                      Err* err) {
+  DCHECK(!complete_);
+  const Value* value = scope->GetValue(var, true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::LIST, err))
+    return false;
+
+  SubstitutionList list;
+  if (!list.Parse(*value, err))
+    return false;
+
+  // Validate the right kinds of patterns are used.
+  if (list.list().empty()) {
+    *err = Err(defined_from(), "\"outputs\" must be specified for this tool.");
+    return false;
+  }
+
+  for (const auto& cur_type : list.required_types()) {
+    if (!IsValidRustSubstitution(cur_type)) {
+      *err = Err(*value, "Pattern not valid here.",
+                 "You used the pattern " + std::string(cur_type->name) +
+                     " which is not valid\nfor this variable.");
+      return false;
+    }
+  }
+
+  *field = std::move(list);
+  return true;
+}
+
+bool RustTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+  // Initialize default vars.
+  if (!Tool::InitTool(scope, toolchain, err)) {
+    return false;
+  }
+
+  // All Rust tools should have outputs.
+  if (!ReadOutputsPatternList(scope, "outputs", &outputs_, err)) {
+    return false;
+  }
+
+  // Check for a sysroot. Sets an empty string when not explicitly set.
+  if (!ReadString(scope, "rust_sysroot", &rust_sysroot_, err)) {
+    return false;
+  }
+  return true;
+}
+
+bool RustTool::ValidateSubstitution(const Substitution* sub_type) const {
+  if (name_ == kRsToolBin || name_ == kRsToolCDylib || name_ == kRsToolDylib ||
+      name_ == kRsToolMacro || name_ == kRsToolRlib ||
+      name_ == kRsToolStaticlib)
+    return IsValidRustSubstitution(sub_type);
+  NOTREACHED();
+  return false;
+}
diff --git a/src/gn/rust_tool.h b/src/gn/rust_tool.h
new file mode 100644 (file)
index 0000000..6a4fdf7
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_TOOL_H_
+#define TOOLS_GN_RUST_TOOL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/rust_values.h"
+#include "gn/source_file.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/target.h"
+#include "gn/tool.h"
+
+class RustTool : public Tool {
+ public:
+  // Rust tools
+  static const char* kRsToolBin;
+  static const char* kRsToolCDylib;
+  static const char* kRsToolDylib;
+  static const char* kRsToolMacro;
+  static const char* kRsToolRlib;
+  static const char* kRsToolStaticlib;
+
+  explicit RustTool(const char* n);
+  ~RustTool();
+
+  // Manual RTTI and required functions ---------------------------------------
+
+  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+  bool ValidateName(const char* name) const override;
+  void SetComplete() override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
+
+  RustTool* AsRust() override;
+  const RustTool* AsRust() const override;
+
+  std::string_view GetSysroot() const;
+
+ private:
+  std::string rust_sysroot_;
+
+  bool SetOutputExtension(const Value* value, std::string* var, Err* err);
+  bool ReadOutputsPatternList(Scope* scope,
+                              const char* var,
+                              SubstitutionList* field,
+                              Err* err);
+
+  DISALLOW_COPY_AND_ASSIGN(RustTool);
+};
+
+#endif  // TOOLS_GN_RUST_TOOL_H_
diff --git a/src/gn/rust_values.cc b/src/gn/rust_values.cc
new file mode 100644 (file)
index 0000000..ed314eb
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_values.h"
+
+RustValues::RustValues() : crate_type_(RustValues::CRATE_AUTO) {}
+
+RustValues::~RustValues() = default;
\ No newline at end of file
diff --git a/src/gn/rust_values.h b/src/gn/rust_values.h
new file mode 100644 (file)
index 0000000..8f7fff6
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_TARGET_VALUES_H_
+#define TOOLS_GN_RUST_TARGET_VALUES_H_
+
+#include <map>
+
+#include "base/containers/flat_map.h"
+#include "gn/inherited_libraries.h"
+#include "gn/label.h"
+#include "gn/source_file.h"
+
+// Holds the values (outputs, args, script name, etc.) for either an action or
+// an action_foreach target.
+class RustValues {
+ public:
+  RustValues();
+  ~RustValues();
+
+  // Library crate types are specified here. Shared library crate types must be
+  // specified, all other crate types can be automatically deduced from the
+  // target type (e.g. executables use crate_type = "bin", static_libraries use
+  // crate_type = "staticlib") unless explicitly set.
+  enum CrateType {
+    CRATE_AUTO = 0,
+    CRATE_BIN,
+    CRATE_CDYLIB,
+    CRATE_DYLIB,
+    CRATE_PROC_MACRO,
+    CRATE_RLIB,
+    CRATE_STATICLIB,
+  };
+
+  // Name of this crate.
+  std::string& crate_name() { return crate_name_; }
+  const std::string& crate_name() const { return crate_name_; }
+
+  // Main source file for this crate.
+  const SourceFile& crate_root() const { return crate_root_; }
+  void set_crate_root(SourceFile& s) { crate_root_ = s; }
+
+  // Crate type for compilation.
+  CrateType crate_type() const { return crate_type_; }
+  void set_crate_type(CrateType s) { crate_type_ = s; }
+
+  // Any renamed dependencies for the `extern` flags.
+  const std::map<Label, std::string>& aliased_deps() const {
+    return aliased_deps_;
+  }
+  std::map<Label, std::string>& aliased_deps() { return aliased_deps_; }
+
+  // Transitive closure of libraries that are depended on by this target
+  InheritedLibraries& transitive_libs() { return rust_libs_; }
+  const InheritedLibraries& transitive_libs() const { return rust_libs_; }
+
+ private:
+  std::string crate_name_;
+  SourceFile crate_root_;
+  CrateType crate_type_ = CRATE_AUTO;
+  std::map<Label, std::string> aliased_deps_;
+  InheritedLibraries rust_libs_;
+
+  DISALLOW_COPY_AND_ASSIGN(RustValues);
+};
+
+#endif  // TOOLS_GN_RUST_TARGET_VALUES_H_
diff --git a/src/gn/rust_values_generator.cc b/src/gn/rust_values_generator.cc
new file mode 100644 (file)
index 0000000..e71aff6
--- /dev/null
@@ -0,0 +1,191 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_values_generator.h"
+
+#include "gn/config_values_generator.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/rust_variables.h"
+#include "gn/scope.h"
+#include "gn/target.h"
+#include "gn/value_extractors.h"
+
+static const char* kRustSupportedCrateTypesError =
+    "\"crate_type\" must be one of \"bin\", \"cdylib\", \"dylib\", or "
+    "\"proc-macro\", \"rlib\", \"staticlib\".";
+
+RustValuesGenerator::RustValuesGenerator(Target* target,
+                                         Scope* scope,
+                                         const FunctionCallNode* function_call,
+                                         Err* err)
+    : target_(target),
+      scope_(scope),
+      function_call_(function_call),
+      err_(err) {}
+
+RustValuesGenerator::~RustValuesGenerator() = default;
+
+void RustValuesGenerator::Run() {
+  // source_set targets don't need any special Rust handling.
+  if (target_->output_type() == Target::SOURCE_SET)
+    return;
+
+  // Check that this type of target is Rust-supported.
+  if (target_->output_type() != Target::EXECUTABLE &&
+      target_->output_type() != Target::SHARED_LIBRARY &&
+      target_->output_type() != Target::RUST_LIBRARY &&
+      target_->output_type() != Target::RUST_PROC_MACRO &&
+      target_->output_type() != Target::STATIC_LIBRARY &&
+      target_->output_type() != Target::LOADABLE_MODULE) {
+    // Only valid rust output types.
+    *err_ = Err(function_call_,
+                "Target type \"" +
+                    std::string(Target::GetStringForOutputType(
+                        target_->output_type())) +
+                    "\" is not supported for Rust compilation.",
+                "Supported target types are \"executable\", \"loadable_module\""
+                "\"shared_library\", \"static_library\", or \"source_set\".");
+    return;
+  }
+
+  if (!FillCrateName())
+    return;
+
+  if (!FillCrateType())
+    return;
+
+  if (!FillCrateRoot())
+    return;
+
+  if (!FillAliasedDeps())
+    return;
+}
+
+bool RustValuesGenerator::FillCrateName() {
+  const Value* value = scope_->GetValue(variables::kRustCrateName, true);
+  if (!value) {
+    // The target name will be used.
+    target_->rust_values().crate_name() = target_->label().name();
+    return true;
+  }
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  target_->rust_values().crate_name() = std::move(value->string_value());
+  return true;
+}
+
+bool RustValuesGenerator::FillCrateType() {
+  const Value* value = scope_->GetValue(variables::kRustCrateType, true);
+  if (!value) {
+    // Require shared_library and loadable_module targets to tell us what
+    // they want.
+    if (target_->output_type() == Target::SHARED_LIBRARY ||
+        target_->output_type() == Target::LOADABLE_MODULE) {
+      *err_ = Err(function_call_,
+                  "Must set \"crate_type\" on a Rust \"shared_library\".",
+                  kRustSupportedCrateTypesError);
+      return false;
+    }
+
+    return true;
+  }
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  if (value->string_value() == "bin") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_BIN);
+    return true;
+  }
+  if (value->string_value() == "cdylib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_CDYLIB);
+    return true;
+  }
+  if (value->string_value() == "dylib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_DYLIB);
+    return true;
+  }
+  if (value->string_value() == "proc-macro") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_PROC_MACRO);
+    return true;
+  }
+  if (value->string_value() == "rlib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_RLIB);
+    return true;
+  }
+  if (value->string_value() == "staticlib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_STATICLIB);
+    return true;
+  }
+
+  *err_ = Err(value->origin(),
+              "Inadmissible crate type \"" + value->string_value() + "\".",
+              kRustSupportedCrateTypesError);
+  return false;
+}
+
+bool RustValuesGenerator::FillCrateRoot() {
+  const Value* value = scope_->GetValue(variables::kRustCrateRoot, true);
+  if (!value) {
+    // If there's only one source, use that.
+    if (target_->sources().size() == 1) {
+      target_->rust_values().set_crate_root(target_->sources()[0]);
+      return true;
+    }
+    // Otherwise, see if "lib.rs" or "main.rs" (as relevant) are in sources.
+    std::string to_find =
+        target_->output_type() == Target::EXECUTABLE ? "main.rs" : "lib.rs";
+    for (auto& source : target_->sources()) {
+      if (source.GetName() == to_find) {
+        target_->rust_values().set_crate_root(source);
+        return true;
+      }
+    }
+    *err_ = Err(function_call_, "Missing \"crate_root\" and missing \"" +
+                                    to_find + "\" in sources.");
+    return false;
+  }
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  SourceFile dest;
+  if (!ExtractRelativeFile(scope_->settings()->build_settings(), *value,
+                           scope_->GetSourceDir(), &dest, err_))
+    return false;
+
+  target_->rust_values().set_crate_root(dest);
+  return true;
+}
+
+bool RustValuesGenerator::FillAliasedDeps() {
+  const Value* value = scope_->GetValue(variables::kRustAliasedDeps, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::SCOPE, err_))
+    return false;
+
+  Scope::KeyValueMap aliased_deps;
+  value->scope_value()->GetCurrentScopeValues(&aliased_deps);
+  for (const auto& pair : aliased_deps) {
+    Label dep_label =
+        Label::Resolve(scope_->GetSourceDir(),
+                       scope_->settings()->build_settings()->root_path_utf8(),
+                       ToolchainLabelForScope(scope_), pair.second, err_);
+
+    if (err_->has_error())
+      return false;
+
+    // Insert into the aliased_deps map.
+    target_->rust_values().aliased_deps().emplace(std::move(dep_label),
+                                                  pair.first);
+  }
+
+  return true;
+}
diff --git a/src/gn/rust_values_generator.h b/src/gn/rust_values_generator.h
new file mode 100644 (file)
index 0000000..d902978
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_VALUES_GENERATOR_H_
+#define TOOLS_GN_RUST_VALUES_GENERATOR_H_
+
+#include "base/macros.h"
+#include "gn/target.h"
+
+class FunctionCallNode;
+
+// Collects and writes specified data.
+class RustValuesGenerator {
+ public:
+  RustValuesGenerator(Target* target,
+                      Scope* scope,
+                      const FunctionCallNode* function_call,
+                      Err* err);
+  ~RustValuesGenerator();
+
+  void Run();
+
+ private:
+  bool FillCrateName();
+  bool FillCrateRoot();
+  bool FillCrateType();
+  bool FillEdition();
+  bool FillAliasedDeps();
+
+  Target* target_;
+  Scope* scope_;
+  const FunctionCallNode* function_call_;
+  Err* err_;
+
+  DISALLOW_COPY_AND_ASSIGN(RustValuesGenerator);
+};
+
+#endif  // TOOLS_GN_RUST_VALUES_GENERATOR_H_
diff --git a/src/gn/rust_variables.cc b/src/gn/rust_variables.cc
new file mode 100644 (file)
index 0000000..651eb07
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_variables.h"
+
+namespace variables {
+
+// Rust target variables ------------------------------------------------------
+
+const char kRustAliasedDeps[] = "aliased_deps";
+const char kRustAliasedDeps_HelpShort[] =
+    "aliased_deps: [scope] Set of crate-dependency pairs.";
+const char kRustAliasedDeps_Help[] =
+    R"(aliased_deps: [scope] Set of crate-dependency pairs.
+
+  Valid for `rust_library` targets and `executable`, `static_library`, and
+  `shared_library` targets that contain Rust sources.
+
+  A scope, each key indicating the renamed crate and the corresponding value
+  specifying the label of the dependency producing the relevant binary.
+
+  All dependencies listed in this field *must* be listed as deps of the target.
+
+    executable("foo") {
+      sources = [ "main.rs" ]
+      deps = [ "//bar" ]
+    }
+
+  This target would compile the `foo` crate with the following `extern` flag:
+  `rustc ...command... --extern bar=<build_out_dir>/obj/bar`
+
+    executable("foo") {
+      sources = [ "main.rs" ]
+      deps = [ ":bar" ]
+      aliased_deps = {
+        bar_renamed = ":bar"
+      }
+    }
+
+  With the addition of `aliased_deps`, above target would instead compile with:
+  `rustc ...command... --extern bar_renamed=<build_out_dir>/obj/bar`
+)";
+
+const char kRustCrateName[] = "crate_name";
+const char kRustCrateName_HelpShort[] =
+    "crate_name: [string] The name for the compiled crate.";
+const char kRustCrateName_Help[] =
+    R"(crate_name: [string] The name for the compiled crate.
+
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  If crate_name is not set, then this rule will use the target name.
+)";
+
+const char kRustCrateType[] = "crate_type";
+const char kRustCrateType_HelpShort[] =
+    "crate_type: [string] The type of linkage to use on a shared_library.";
+const char kRustCrateType_Help[] =
+    R"(crate_type: [string] The type of linkage to use on a shared_library.
+
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  Options for this field are "cdylib", "staticlib", "proc-macro", and "dylib".
+  This field sets the `crate-type` attribute for the `rustc` tool on static
+  libraries, as well as the appropriate output extension in the
+  `rust_output_extension` attribute. Since outputs must be explicit, the `lib`
+  crate type (where the Rust compiler produces what it thinks is the
+  appropriate library type) is not supported.
+
+  It should be noted that the "dylib" crate type in Rust is unstable in the set
+  of symbols it exposes, and most usages today are potentially wrong and will
+  be broken in the future.
+
+  Static libraries, rust libraries, and executables have this field set
+  automatically.
+)";
+
+const char kRustCrateRoot[] = "crate_root";
+const char kRustCrateRoot_HelpShort[] =
+    "crate_root: [string] The root source file for a binary or library.";
+const char kRustCrateRoot_Help[] =
+    R"(crate_root: [string] The root source file for a binary or library.
+
+  Valid for `rust_library` targets and `executable`, `static_library`,
+  `shared_library`, and `source_set` targets that contain Rust sources.
+
+  This file is usually the `main.rs` or `lib.rs` for binaries and libraries,
+  respectively.
+
+  If crate_root is not set, then this rule will look for a lib.rs file (or
+  main.rs for executable) or a single file in sources, if sources contains
+  only one file.
+)";
+
+void InsertRustVariables(VariableInfoMap* info_map) {
+  info_map->insert(std::make_pair(
+      kRustAliasedDeps,
+      VariableInfo(kRustAliasedDeps_HelpShort, kRustAliasedDeps_Help)));
+  info_map->insert(std::make_pair(
+      kRustCrateName,
+      VariableInfo(kRustCrateName_HelpShort, kRustCrateName_Help)));
+  info_map->insert(std::make_pair(
+      kRustCrateType,
+      VariableInfo(kRustCrateType_HelpShort, kRustCrateType_Help)));
+  info_map->insert(std::make_pair(
+      kRustCrateRoot,
+      VariableInfo(kRustCrateRoot_HelpShort, kRustCrateRoot_Help)));
+}
+
+}  // namespace variables
diff --git a/src/gn/rust_variables.h b/src/gn/rust_variables.h
new file mode 100644 (file)
index 0000000..ea2da6d
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_VARIABLES_H_
+#define TOOLS_GN_RUST_VARIABLES_H_
+
+#include "gn/variables.h"
+
+namespace variables {
+
+// Rust target vars ------------------------------------------------------
+
+extern const char kRustAliasedDeps[];
+extern const char kRustAliasedDeps_HelpShort[];
+extern const char kRustAliasedDeps_Help[];
+
+extern const char kRustCrateName[];
+extern const char kRustCrateName_HelpShort[];
+extern const char kRustCrateName_Help[];
+
+extern const char kRustCrateType[];
+extern const char kRustCrateType_HelpShort[];
+extern const char kRustCrateType_Help[];
+
+extern const char kRustCrateRoot[];
+extern const char kRustCrateRoot_HelpShort[];
+extern const char kRustCrateRoot_Help[];
+
+extern const char kRustEdition[];
+extern const char kRustEdition_HelpShort[];
+extern const char kRustEdition_Help[];
+
+void InsertRustVariables(VariableInfoMap* info_map);
+
+}  // namespace variables
+
+#endif  // TOOLS_GN_RUST_VARIABLES_H_
\ No newline at end of file
diff --git a/src/gn/scheduler.cc b/src/gn/scheduler.cc
new file mode 100644 (file)
index 0000000..6525f33
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scheduler.h"
+
+#include <algorithm>
+
+#include "gn/standard_out.h"
+#include "gn/target.h"
+
+namespace {}  // namespace
+
+Scheduler* g_scheduler = nullptr;
+
+Scheduler::Scheduler()
+    : main_thread_run_loop_(MsgLoop::Current()),
+      input_file_manager_(new InputFileManager) {
+  g_scheduler = this;
+}
+
+Scheduler::~Scheduler() {
+  WaitForPoolTasks();
+  g_scheduler = nullptr;
+}
+
+bool Scheduler::Run() {
+  main_thread_run_loop_->Run();
+  bool local_is_failed;
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+    local_is_failed = is_failed();
+    has_been_shutdown_ = true;
+  }
+  // Don't do this while holding |lock_|, since it will block on the workers,
+  // which may be in turn waiting on the lock.
+  WaitForPoolTasks();
+  return !local_is_failed;
+}
+
+void Scheduler::Log(const std::string& verb, const std::string& msg) {
+  task_runner()->PostTask([this, verb, msg]() { LogOnMainThread(verb, msg); });
+}
+
+void Scheduler::FailWithError(const Err& err) {
+  DCHECK(err.has_error());
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    if (is_failed_ || has_been_shutdown_)
+      return;  // Ignore errors once we see one.
+    is_failed_ = true;
+  }
+
+  task_runner()->PostTask([this, err]() { FailWithErrorOnMainThread(err); });
+}
+
+void Scheduler::ScheduleWork(std::function<void()> work) {
+  IncrementWorkCount();
+  pool_work_count_.Increment();
+  worker_pool_.PostTask([this, work = std::move(work)]() {
+    work();
+    DecrementWorkCount();
+    if (!pool_work_count_.Decrement()) {
+      std::unique_lock<std::mutex> auto_lock(pool_work_count_lock_);
+      pool_work_count_cv_.notify_one();
+    }
+  });
+}
+
+void Scheduler::AddGenDependency(const base::FilePath& file) {
+  std::lock_guard<std::mutex> lock(lock_);
+  gen_dependencies_.push_back(file);
+}
+
+std::vector<base::FilePath> Scheduler::GetGenDependencies() const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return gen_dependencies_;
+}
+
+void Scheduler::AddWrittenFile(const SourceFile& file) {
+  std::lock_guard<std::mutex> lock(lock_);
+  written_files_.push_back(file);
+}
+
+void Scheduler::AddUnknownGeneratedInput(const Target* target,
+                                         const SourceFile& file) {
+  std::lock_guard<std::mutex> lock(lock_);
+  unknown_generated_inputs_.insert(std::make_pair(file, target));
+}
+
+void Scheduler::AddWriteRuntimeDepsTarget(const Target* target) {
+  std::lock_guard<std::mutex> lock(lock_);
+  write_runtime_deps_targets_.push_back(target);
+}
+
+std::vector<const Target*> Scheduler::GetWriteRuntimeDepsTargets() const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return write_runtime_deps_targets_;
+}
+
+bool Scheduler::IsFileGeneratedByWriteRuntimeDeps(
+    const OutputFile& file) const {
+  std::lock_guard<std::mutex> lock(lock_);
+  // Number of targets should be quite small, so brute-force search is fine.
+  for (const Target* target : write_runtime_deps_targets_) {
+    if (file == target->write_runtime_deps_output()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void Scheduler::AddGeneratedFile(const SourceFile& entry) {
+  std::lock_guard<std::mutex> lock(lock_);
+  generated_files_.insert(std::make_pair(entry, true));
+}
+
+bool Scheduler::IsFileGeneratedByTarget(const SourceFile& file) const {
+  std::lock_guard<std::mutex> lock(lock_);
+  return generated_files_.find(file) != generated_files_.end();
+}
+
+std::multimap<SourceFile, const Target*> Scheduler::GetUnknownGeneratedInputs()
+    const {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  // Remove all unknown inputs that were written files. These are OK as inputs
+  // to build steps since they were written as a side-effect of running GN.
+  //
+  // It's assumed that this function is called once during cleanup to check for
+  // errors, so performing this work in the lock doesn't matter.
+  std::multimap<SourceFile, const Target*> filtered = unknown_generated_inputs_;
+  for (const SourceFile& file : written_files_)
+    filtered.erase(file);
+
+  return filtered;
+}
+
+void Scheduler::ClearUnknownGeneratedInputsAndWrittenFiles() {
+  std::lock_guard<std::mutex> lock(lock_);
+  unknown_generated_inputs_.clear();
+  written_files_.clear();
+}
+
+void Scheduler::IncrementWorkCount() {
+  work_count_.Increment();
+}
+
+void Scheduler::DecrementWorkCount() {
+  if (!work_count_.Decrement()) {
+    task_runner()->PostTask([this]() { OnComplete(); });
+  }
+}
+
+void Scheduler::SuppressOutputForTesting(bool suppress) {
+  std::lock_guard<std::mutex> lock(lock_);
+  suppress_output_for_testing_ = suppress;
+}
+
+void Scheduler::LogOnMainThread(const std::string& verb,
+                                const std::string& msg) {
+  OutputString(verb, DECORATION_YELLOW);
+  OutputString(" " + msg + "\n");
+}
+
+void Scheduler::FailWithErrorOnMainThread(const Err& err) {
+  if (!suppress_output_for_testing_)
+    err.PrintToStdout();
+  task_runner()->PostQuit();
+}
+
+void Scheduler::OnComplete() {
+  // Should be called on the main thread.
+  DCHECK(task_runner() == MsgLoop::Current());
+  task_runner()->PostQuit();
+}
+
+void Scheduler::WaitForPoolTasks() {
+  std::unique_lock<std::mutex> lock(pool_work_count_lock_);
+  while (!pool_work_count_.IsZero())
+    pool_work_count_cv_.wait(lock);
+}
diff --git a/src/gn/scheduler.h b/src/gn/scheduler.h
new file mode 100644 (file)
index 0000000..6fd25fd
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SCHEDULER_H_
+#define TOOLS_GN_SCHEDULER_H_
+
+#include <condition_variable>
+#include <functional>
+#include <map>
+#include <mutex>
+
+#include "base/atomic_ref_count.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gn/input_file_manager.h"
+#include "gn/label.h"
+#include "gn/source_file.h"
+#include "gn/token.h"
+#include "util/msg_loop.h"
+#include "util/worker_pool.h"
+
+class Target;
+
+// Maintains the thread pool and error state.
+class Scheduler {
+ public:
+  Scheduler();
+  ~Scheduler();
+
+  bool Run();
+
+  MsgLoop* task_runner() {
+    DCHECK(main_thread_run_loop_);
+    return main_thread_run_loop_;
+  }
+
+  InputFileManager* input_file_manager() { return input_file_manager_.get(); }
+
+  bool verbose_logging() const { return verbose_logging_; }
+  void set_verbose_logging(bool v) { verbose_logging_ = v; }
+
+  // TODO(brettw) data race on this access (benign?).
+  bool is_failed() const { return is_failed_; }
+
+  void Log(const std::string& verb, const std::string& msg);
+  void FailWithError(const Err& err);
+
+  void ScheduleWork(std::function<void()> work);
+
+  void Shutdown();
+
+  // Declares that the given file was read and affected the build output.
+  //
+  // Some consumers expect provided path to be absolute.kk
+  //
+  // TODO(brettw) this is global rather than per-BuildSettings. If we
+  // start using >1 build settings, then we probably want this to take a
+  // BuildSettings object so we know the dependency on a per-build basis.
+  // If moved, most of the Add/Get functions below should move as well.
+  void AddGenDependency(const base::FilePath& file);
+  std::vector<base::FilePath> GetGenDependencies() const;
+
+  // Tracks calls to write_file for resolving with the unknown generated
+  // inputs (see AddUnknownGeneratedInput below).
+  void AddWrittenFile(const SourceFile& file);
+
+  // Schedules a file to be written due to a target setting write_runtime_deps.
+  void AddWriteRuntimeDepsTarget(const Target* entry);
+  std::vector<const Target*> GetWriteRuntimeDepsTargets() const;
+  bool IsFileGeneratedByWriteRuntimeDeps(const OutputFile& file) const;
+
+  // Tracks generated_file calls.
+  void AddGeneratedFile(const SourceFile& entry);
+  bool IsFileGeneratedByTarget(const SourceFile& file) const;
+
+  // Unknown generated inputs are files that a target declares as an input
+  // in the output directory, but which aren't generated by any dependency.
+  //
+  // Some of these files will be files written by write_file and will be
+  // GenDependencies (see AddWrittenFile above). There are OK and include
+  // things like response files for scripts. Others cases will be ones where
+  // the file is generated by a target that's not a dependency.
+  //
+  // In order to distinguish these two cases, the checking for these input
+  // files needs to be done after all targets are complete. This also has the
+  // nice side effect that if a target generates the file we can find it and
+  // tell the user which dependency is missing.
+  //
+  // The result returned by GetUnknownGeneratedInputs will not count any files
+  // that were written by write_file during execution.
+  void AddUnknownGeneratedInput(const Target* target, const SourceFile& file);
+  std::multimap<SourceFile, const Target*> GetUnknownGeneratedInputs() const;
+  void ClearUnknownGeneratedInputsAndWrittenFiles();  // For testing.
+
+  // We maintain a count of the things we need to do that works like a
+  // refcount. When this reaches 0, the program exits.
+  void IncrementWorkCount();
+  void DecrementWorkCount();
+
+  void SuppressOutputForTesting(bool suppress);
+
+ private:
+  void LogOnMainThread(const std::string& verb, const std::string& msg);
+  void FailWithErrorOnMainThread(const Err& err);
+
+  void DoTargetFileWrite(const Target* target);
+
+  void OnComplete();
+
+  // Waits for tasks scheduled via ScheduleWork() to complete their execution.
+  void WaitForPoolTasks();
+
+  MsgLoop* main_thread_run_loop_;
+
+  scoped_refptr<InputFileManager> input_file_manager_;
+
+  bool verbose_logging_ = false;
+
+  base::AtomicRefCount work_count_;
+
+  // Number of tasks scheduled by ScheduleWork() that haven't completed their
+  // execution.
+  base::AtomicRefCount pool_work_count_;
+
+  // Lock for |pool_work_count_cv_|.
+  std::mutex pool_work_count_lock_;
+
+  // Condition variable signaled when |pool_work_count_| reaches zero.
+  std::condition_variable pool_work_count_cv_;
+
+  WorkerPool worker_pool_;
+
+  mutable std::mutex lock_;
+  bool is_failed_ = false;
+
+  bool suppress_output_for_testing_ = false;
+
+  // Used to track whether the worker pool has been shutdown. This is necessary
+  // to clean up after tests that make a scheduler but don't run the message
+  // loop.
+  bool has_been_shutdown_ = false;
+
+  // Protected by the lock. See the corresponding Add/Get functions above.
+  std::vector<base::FilePath> gen_dependencies_;
+  std::vector<SourceFile> written_files_;
+  std::vector<const Target*> write_runtime_deps_targets_;
+  std::multimap<SourceFile, const Target*> unknown_generated_inputs_;
+  std::map<SourceFile, bool> generated_files_;
+
+  DISALLOW_COPY_AND_ASSIGN(Scheduler);
+};
+
+extern Scheduler* g_scheduler;
+
+#endif  // TOOLS_GN_SCHEDULER_H_
diff --git a/src/gn/scope.cc b/src/gn/scope.cc
new file mode 100644 (file)
index 0000000..7e92766
--- /dev/null
@@ -0,0 +1,573 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scope.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "gn/parse_tree.h"
+#include "gn/source_file.h"
+#include "gn/template.h"
+
+namespace {
+
+// FLags set in the mode_flags_ of a scope. If a bit is set, it applies
+// recursively to all dependent scopes.
+const unsigned kProcessingBuildConfigFlag = 1;
+const unsigned kProcessingImportFlag = 2;
+
+// Returns true if this variable name should be considered private. Private
+// values start with an underscore, and are not imported from "gni" files
+// when processing an import.
+bool IsPrivateVar(const std::string_view& name) {
+  return name.empty() || name[0] == '_';
+}
+
+}  // namespace
+
+// Defaults to all false, which are the things least likely to cause errors.
+Scope::MergeOptions::MergeOptions()
+    : clobber_existing(false),
+      skip_private_vars(false),
+      mark_dest_used(false) {}
+
+Scope::MergeOptions::~MergeOptions() = default;
+
+Scope::ProgrammaticProvider::~ProgrammaticProvider() {
+  scope_->RemoveProvider(this);
+}
+
+Scope::Scope(const Settings* settings)
+    : const_containing_(nullptr),
+      mutable_containing_(nullptr),
+      settings_(settings),
+      mode_flags_(0),
+      item_collector_(nullptr) {}
+
+Scope::Scope(Scope* parent)
+    : const_containing_(nullptr),
+      mutable_containing_(parent),
+      settings_(parent->settings()),
+      mode_flags_(0),
+      item_collector_(nullptr),
+      build_dependency_files_(parent->build_dependency_files_) {}
+
+Scope::Scope(const Scope* parent)
+    : const_containing_(parent),
+      mutable_containing_(nullptr),
+      settings_(parent->settings()),
+      mode_flags_(0),
+      item_collector_(nullptr),
+      build_dependency_files_(parent->build_dependency_files_) {}
+
+Scope::~Scope() = default;
+
+void Scope::DetachFromContaining() {
+  const_containing_ = nullptr;
+  mutable_containing_ = nullptr;
+}
+
+bool Scope::HasValues(SearchNested search_nested) const {
+  DCHECK(search_nested == SEARCH_CURRENT);
+  return !values_.empty();
+}
+
+const Value* Scope::GetValue(const std::string_view& ident,
+                             bool counts_as_used) {
+  const Scope* found_in_scope = nullptr;
+  return GetValueWithScope(ident, counts_as_used, &found_in_scope);
+}
+
+const Value* Scope::GetValueWithScope(const std::string_view& ident,
+                                      bool counts_as_used,
+                                      const Scope** found_in_scope) {
+  // First check for programmatically-provided values.
+  for (auto* provider : programmatic_providers_) {
+    const Value* v = provider->GetProgrammaticValue(ident);
+    if (v) {
+      *found_in_scope = nullptr;
+      return v;
+    }
+  }
+
+  RecordMap::iterator found = values_.find(ident);
+  if (found != values_.end()) {
+    if (counts_as_used)
+      found->second.used = true;
+    *found_in_scope = this;
+    return &found->second.value;
+  }
+
+  // Search in the parent scope.
+  if (const_containing_)
+    return const_containing_->GetValueWithScope(ident, found_in_scope);
+  if (mutable_containing_) {
+    return mutable_containing_->GetValueWithScope(ident, counts_as_used,
+                                                  found_in_scope);
+  }
+  return nullptr;
+}
+
+Value* Scope::GetMutableValue(const std::string_view& ident,
+                              SearchNested search_mode,
+                              bool counts_as_used) {
+  // Don't do programmatic values, which are not mutable.
+  RecordMap::iterator found = values_.find(ident);
+  if (found != values_.end()) {
+    if (counts_as_used)
+      found->second.used = true;
+    return &found->second.value;
+  }
+
+  // Search in the parent mutable scope if requested, but not const one.
+  if (search_mode == SEARCH_NESTED && mutable_containing_) {
+    return mutable_containing_->GetMutableValue(ident, Scope::SEARCH_NESTED,
+                                                counts_as_used);
+  }
+  return nullptr;
+}
+
+std::string_view Scope::GetStorageKey(const std::string_view& ident) const {
+  RecordMap::const_iterator found = values_.find(ident);
+  if (found != values_.end())
+    return found->first;
+
+  // Search in parent scope.
+  if (containing())
+    return containing()->GetStorageKey(ident);
+  return std::string_view();
+}
+
+const Value* Scope::GetValue(const std::string_view& ident) const {
+  const Scope* found_in_scope = nullptr;
+  return GetValueWithScope(ident, &found_in_scope);
+}
+
+const Value* Scope::GetValueWithScope(const std::string_view& ident,
+                                      const Scope** found_in_scope) const {
+  RecordMap::const_iterator found = values_.find(ident);
+  if (found != values_.end()) {
+    *found_in_scope = this;
+    return &found->second.value;
+  }
+  if (containing())
+    return containing()->GetValueWithScope(ident, found_in_scope);
+  return nullptr;
+}
+
+Value* Scope::SetValue(const std::string_view& ident,
+                       Value v,
+                       const ParseNode* set_node) {
+  Record& r = values_[ident];  // Clears any existing value.
+  r.value = std::move(v);
+  r.value.set_origin(set_node);
+  return &r.value;
+}
+
+void Scope::RemoveIdentifier(const std::string_view& ident) {
+  RecordMap::iterator found = values_.find(ident);
+  if (found != values_.end())
+    values_.erase(found);
+}
+
+void Scope::RemovePrivateIdentifiers() {
+  // Do it in two phases to avoid mutating while iterating. Our hash map is
+  // currently backed by several different vendor-specific implementations and
+  // I'm not sure if all of them support mutating while iterating. Since this
+  // is not perf-critical, do the safe thing.
+  std::vector<std::string_view> to_remove;
+  for (const auto& cur : values_) {
+    if (IsPrivateVar(cur.first))
+      to_remove.push_back(cur.first);
+  }
+
+  for (const auto& cur : to_remove)
+    values_.erase(cur);
+}
+
+bool Scope::AddTemplate(const std::string& name, const Template* templ) {
+  if (GetTemplate(name))
+    return false;
+  templates_[name] = templ;
+  return true;
+}
+
+const Template* Scope::GetTemplate(const std::string& name) const {
+  TemplateMap::const_iterator found = templates_.find(name);
+  if (found != templates_.end())
+    return found->second.get();
+  if (containing())
+    return containing()->GetTemplate(name);
+  return nullptr;
+}
+
+void Scope::MarkUsed(const std::string_view& ident) {
+  RecordMap::iterator found = values_.find(ident);
+  if (found == values_.end()) {
+    NOTREACHED();
+    return;
+  }
+  found->second.used = true;
+}
+
+void Scope::MarkAllUsed() {
+  for (auto& cur : values_)
+    cur.second.used = true;
+}
+
+void Scope::MarkAllUsed(const std::set<std::string>& excluded_values) {
+  for (auto& cur : values_) {
+    if (!excluded_values.empty() &&
+        excluded_values.find(std::string(cur.first)) != excluded_values.end()) {
+      continue;  // Skip this excluded value.
+    }
+    cur.second.used = true;
+  }
+}
+
+void Scope::MarkUnused(const std::string_view& ident) {
+  RecordMap::iterator found = values_.find(ident);
+  if (found == values_.end()) {
+    NOTREACHED();
+    return;
+  }
+  found->second.used = false;
+}
+
+bool Scope::IsSetButUnused(const std::string_view& ident) const {
+  RecordMap::const_iterator found = values_.find(ident);
+  if (found != values_.end()) {
+    if (!found->second.used) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool Scope::CheckForUnusedVars(Err* err) const {
+  for (const auto& pair : values_) {
+    if (!pair.second.used) {
+      std::string help =
+          "You set the variable \"" + std::string(pair.first) +
+          "\" here and it was unused before it went\nout of scope.";
+
+      const BinaryOpNode* binary = pair.second.value.origin()->AsBinaryOp();
+      if (binary && binary->op().type() == Token::EQUAL) {
+        // Make a nicer error message for normal var sets.
+        *err =
+            Err(binary->left()->GetRange(), "Assignment had no effect.", help);
+      } else {
+        // This will happen for internally-generated variables.
+        *err =
+            Err(pair.second.value.origin(), "Assignment had no effect.", help);
+      }
+      return false;
+    }
+  }
+  return true;
+}
+
+void Scope::GetCurrentScopeValues(KeyValueMap* output) const {
+  for (const auto& pair : values_)
+    (*output)[pair.first] = pair.second.value;
+}
+
+bool Scope::CheckCurrentScopeValuesEqual(const Scope* other) const {
+  // If there are containing scopes, equality shouldn't work.
+  if (containing()) {
+    return false;
+  }
+  if (values_.size() != other->values_.size()) {
+    return false;
+  }
+  for (const auto& pair : values_) {
+    const Value* v = other->GetValue(pair.first);
+    if (!v || *v != pair.second.value) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Scope::NonRecursiveMergeTo(Scope* dest,
+                                const MergeOptions& options,
+                                const ParseNode* node_for_err,
+                                const char* desc_for_err,
+                                Err* err) const {
+  // Values.
+  for (const auto& pair : values_) {
+    const std::string_view& current_name = pair.first;
+    if (options.skip_private_vars && IsPrivateVar(current_name))
+      continue;  // Skip this private var.
+    if (!options.excluded_values.empty() &&
+        options.excluded_values.find(std::string(current_name)) !=
+            options.excluded_values.end()) {
+      continue;  // Skip this excluded value.
+    }
+
+    const Value& new_value = pair.second.value;
+    if (!options.clobber_existing) {
+      const Value* existing_value = dest->GetValue(current_name);
+      if (existing_value && new_value != *existing_value) {
+        // Value present in both the source and the dest.
+        std::string desc_string(desc_for_err);
+        *err = Err(node_for_err, "Value collision.",
+                   "This " + desc_string + " contains \"" +
+                       std::string(current_name) + "\"");
+        err->AppendSubErr(
+            Err(pair.second.value, "defined here.",
+                "Which would clobber the one in your current scope"));
+        err->AppendSubErr(
+            Err(*existing_value, "defined here.",
+                "Executing " + desc_string +
+                    " should not conflict with anything "
+                    "in the current\nscope unless the values are identical."));
+        return false;
+      }
+    }
+    dest->values_[current_name] = pair.second;
+
+    if (options.mark_dest_used)
+      dest->MarkUsed(current_name);
+  }
+
+  // Target defaults are owning pointers.
+  for (const auto& pair : target_defaults_) {
+    const std::string& current_name = pair.first;
+    if (!options.excluded_values.empty() &&
+        options.excluded_values.find(current_name) !=
+            options.excluded_values.end()) {
+      continue;  // Skip the excluded value.
+    }
+
+    if (!options.clobber_existing) {
+      const Scope* dest_defaults = dest->GetTargetDefaults(current_name);
+      if (dest_defaults) {
+        if (RecordMapValuesEqual(pair.second->values_,
+                                 dest_defaults->values_)) {
+          // Values of the two defaults are equivalent, just ignore the
+          // collision.
+          continue;
+        } else {
+          // TODO(brettw) it would be nice to know the origin of a
+          // set_target_defaults so we can give locations for the colliding
+          // target defaults.
+          std::string desc_string(desc_for_err);
+          *err = Err(node_for_err, "Target defaults collision.",
+                     "This " + desc_string +
+                         " contains target defaults for\n"
+                         "\"" +
+                         current_name +
+                         "\" which would clobber one for the\n"
+                         "same target type in your current scope. It's "
+                         "unfortunate that "
+                         "I'm too stupid\nto tell you the location of where "
+                         "the target "
+                         "defaults were set. Usually\nthis happens in the "
+                         "BUILDCONFIG.gn "
+                         "file or in a related .gni file.\n");
+          return false;
+        }
+      }
+    }
+
+    std::unique_ptr<Scope>& dest_scope = dest->target_defaults_[current_name];
+    dest_scope = std::make_unique<Scope>(settings_);
+    pair.second->NonRecursiveMergeTo(dest_scope.get(), options, node_for_err,
+                                     "<SHOULDN'T HAPPEN>", err);
+  }
+
+  // Templates.
+  for (const auto& pair : templates_) {
+    const std::string& current_name = pair.first;
+    if (options.skip_private_vars && IsPrivateVar(current_name))
+      continue;  // Skip this private template.
+    if (!options.excluded_values.empty() &&
+        options.excluded_values.find(current_name) !=
+            options.excluded_values.end()) {
+      continue;  // Skip the excluded value.
+    }
+
+    if (!options.clobber_existing) {
+      const Template* existing_template = dest->GetTemplate(current_name);
+      // Since templates are refcounted, we can check if it's the same one by
+      // comparing pointers.
+      if (existing_template && pair.second.get() != existing_template) {
+        // Rule present in both the source and the dest, and they're not the
+        // same one.
+        std::string desc_string(desc_for_err);
+        *err = Err(node_for_err, "Template collision.",
+                   "This " + desc_string + " contains a template \"" +
+                       current_name + "\"");
+        err->AppendSubErr(
+            Err(pair.second->GetDefinitionRange(), "defined here.",
+                "Which would clobber the one in your current scope"));
+        err->AppendSubErr(Err(existing_template->GetDefinitionRange(),
+                              "defined here.",
+                              "Executing " + desc_string +
+                                  " should not conflict with anything "
+                                  "in the current\nscope."));
+        return false;
+      }
+    }
+
+    // Be careful to delete any pointer we're about to clobber.
+    dest->templates_[current_name] = pair.second;
+  }
+
+  // Propagate build dependency files,
+  dest->AddBuildDependencyFiles(build_dependency_files_);
+
+  return true;
+}
+
+std::unique_ptr<Scope> Scope::MakeClosure() const {
+  std::unique_ptr<Scope> result;
+  if (const_containing_) {
+    // We reached the top of the mutable scope stack. The result scope just
+    // references the const scope (which will never change).
+    result = std::make_unique<Scope>(const_containing_);
+  } else if (mutable_containing_) {
+    // There are more nested mutable scopes. Recursively go up the stack to
+    // get the closure.
+    result = mutable_containing_->MakeClosure();
+  } else {
+    // This is a standalone scope, just copy it.
+    result = std::make_unique<Scope>(settings_);
+  }
+
+  // Want to clobber since we've flattened some nested scopes, and our parent
+  // scope may have a duplicate value set.
+  MergeOptions options;
+  options.clobber_existing = true;
+
+  // Add in our variables and we're done.
+  Err err;
+  NonRecursiveMergeTo(result.get(), options, nullptr, "<SHOULDN'T HAPPEN>",
+                      &err);
+  DCHECK(!err.has_error());
+  return result;
+}
+
+Scope* Scope::MakeTargetDefaults(const std::string& target_type) {
+  std::unique_ptr<Scope>& dest = target_defaults_[target_type];
+  dest = std::make_unique<Scope>(settings_);
+  return dest.get();
+}
+
+const Scope* Scope::GetTargetDefaults(const std::string& target_type) const {
+  NamedScopeMap::const_iterator found = target_defaults_.find(target_type);
+  if (found != target_defaults_.end())
+    return found->second.get();
+  if (containing())
+    return containing()->GetTargetDefaults(target_type);
+  return nullptr;
+}
+
+void Scope::SetProcessingBuildConfig() {
+  DCHECK((mode_flags_ & kProcessingBuildConfigFlag) == 0);
+  mode_flags_ |= kProcessingBuildConfigFlag;
+}
+
+void Scope::ClearProcessingBuildConfig() {
+  DCHECK(mode_flags_ & kProcessingBuildConfigFlag);
+  mode_flags_ &= ~(kProcessingBuildConfigFlag);
+}
+
+bool Scope::IsProcessingBuildConfig() const {
+  if (mode_flags_ & kProcessingBuildConfigFlag)
+    return true;
+  if (containing())
+    return containing()->IsProcessingBuildConfig();
+  return false;
+}
+
+void Scope::SetProcessingImport() {
+  DCHECK((mode_flags_ & kProcessingImportFlag) == 0);
+  mode_flags_ |= kProcessingImportFlag;
+}
+
+void Scope::ClearProcessingImport() {
+  DCHECK(mode_flags_ & kProcessingImportFlag);
+  mode_flags_ &= ~(kProcessingImportFlag);
+}
+
+bool Scope::IsProcessingImport() const {
+  if (mode_flags_ & kProcessingImportFlag)
+    return true;
+  if (containing())
+    return containing()->IsProcessingImport();
+  return false;
+}
+
+const SourceDir& Scope::GetSourceDir() const {
+  if (!source_dir_.is_null())
+    return source_dir_;
+  if (containing())
+    return containing()->GetSourceDir();
+  return source_dir_;
+}
+
+void Scope::AddBuildDependencyFile(const SourceFile& build_dependency_file) {
+  build_dependency_files_.insert(build_dependency_file);
+}
+
+void Scope::AddBuildDependencyFiles(
+    const SourceFileSet& build_dependency_files) {
+  build_dependency_files_.insert(build_dependency_files.begin(),
+                                 build_dependency_files.end());
+}
+
+Scope::ItemVector* Scope::GetItemCollector() {
+  if (item_collector_)
+    return item_collector_;
+  if (mutable_containing())
+    return mutable_containing()->GetItemCollector();
+  return nullptr;
+}
+
+void Scope::SetProperty(const void* key, void* value) {
+  if (!value) {
+    DCHECK(properties_.find(key) != properties_.end());
+    properties_.erase(key);
+  } else {
+    properties_[key] = value;
+  }
+}
+
+void* Scope::GetProperty(const void* key, const Scope** found_on_scope) const {
+  PropertyMap::const_iterator found = properties_.find(key);
+  if (found != properties_.end()) {
+    if (found_on_scope)
+      *found_on_scope = this;
+    return found->second;
+  }
+  if (containing())
+    return containing()->GetProperty(key, found_on_scope);
+  return nullptr;
+}
+
+void Scope::AddProvider(ProgrammaticProvider* p) {
+  programmatic_providers_.insert(p);
+}
+
+void Scope::RemoveProvider(ProgrammaticProvider* p) {
+  DCHECK(programmatic_providers_.find(p) != programmatic_providers_.end());
+  programmatic_providers_.erase(p);
+}
+
+// static
+bool Scope::RecordMapValuesEqual(const RecordMap& a, const RecordMap& b) {
+  if (a.size() != b.size())
+    return false;
+  for (const auto& pair : a) {
+    const auto& found_b = b.find(pair.first);
+    if (found_b == b.end())
+      return false;  // Item in 'a' but not 'b'.
+    if (pair.second.value != found_b->second.value)
+      return false;  // Values for variable in 'a' and 'b' are different.
+  }
+  return true;
+}
diff --git a/src/gn/scope.h b/src/gn/scope.h
new file mode 100644 (file)
index 0000000..e6a2f5f
--- /dev/null
@@ -0,0 +1,387 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SCOPE_H_
+#define TOOLS_GN_SCOPE_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "gn/err.h"
+#include "gn/pattern.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/value.h"
+
+class Item;
+class ParseNode;
+class Settings;
+class Template;
+
+// Scope for the script execution.
+//
+// Scopes are nested. Writing goes into the toplevel scope, reading checks
+// values recursively down the stack until a match is found or there are no
+// more containing scopes.
+//
+// A containing scope can be const or non-const. The const containing scope is
+// used primarily to refer to the master build config which is shared across
+// many invocations. A const containing scope, however, prevents us from
+// marking variables "used" which prevents us from issuing errors on unused
+// variables. So you should use a non-const containing scope whenever possible.
+class Scope {
+ public:
+  using KeyValueMap = std::map<std::string_view, Value>;
+  // Holds an owning list of Items.
+  using ItemVector = std::vector<std::unique_ptr<Item>>;
+
+  // A flag to indicate whether a function should recurse into nested scopes,
+  // or only operate on the current scope.
+  enum SearchNested { SEARCH_NESTED, SEARCH_CURRENT };
+
+  // Allows code to provide values for built-in variables. This class will
+  // automatically register itself on construction and deregister itself on
+  // destruction.
+  class ProgrammaticProvider {
+   public:
+    explicit ProgrammaticProvider(Scope* scope) : scope_(scope) {
+      scope_->AddProvider(this);
+    }
+    virtual ~ProgrammaticProvider();
+
+    // Returns a non-null value if the given value can be programmatically
+    // generated, or NULL if there is none.
+    virtual const Value* GetProgrammaticValue(
+        const std::string_view& ident) = 0;
+
+   protected:
+    Scope* scope_;
+  };
+
+  // Options for configuring scope merges.
+  struct MergeOptions {
+    MergeOptions();
+    ~MergeOptions();
+
+    // When set, all existing avlues in the destination scope will be
+    // overwritten.
+    //
+    // When false, it will be an error to merge a variable into another scope
+    // where a variable with the same name is already set. The exception is
+    // if both of the variables have the same value (which happens if you
+    // somehow multiply import the same file, for example). This case will be
+    // ignored since there is nothing getting lost.
+    bool clobber_existing;
+
+    // When true, private variables (names beginning with an underscore) will
+    // be copied to the destination scope. When false, private values will be
+    // skipped.
+    bool skip_private_vars;
+
+    // When set, values copied to the destination scope will be marked as used
+    // so won't trigger an unused variable warning. You want this when doing an
+    // import, for example, or files that don't need a variable from the .gni
+    // file will throw an error.
+    bool mark_dest_used;
+
+    // When set, those variables are not merged.
+    std::set<std::string> excluded_values;
+  };
+
+  // Creates an empty toplevel scope.
+  explicit Scope(const Settings* settings);
+
+  // Creates a dependent scope.
+  explicit Scope(Scope* parent);
+  explicit Scope(const Scope* parent);
+
+  ~Scope();
+
+  const Settings* settings() const { return settings_; }
+
+  // See the const_/mutable_containing_ var declarations below. Yes, it's a
+  // bit weird that we can have a const pointer to the "mutable" one.
+  Scope* mutable_containing() { return mutable_containing_; }
+  const Scope* mutable_containing() const { return mutable_containing_; }
+  const Scope* const_containing() const { return const_containing_; }
+  const Scope* containing() const {
+    return mutable_containing_ ? mutable_containing_ : const_containing_;
+  }
+
+  // Clears any references to containing scopes. This scope will now be
+  // self-sufficient.
+  void DetachFromContaining();
+
+  // Returns true if the scope has any values set. This does not check other
+  // things that may be set like templates or defaults.
+  //
+  // Currently this does not search nested scopes and this will assert if you
+  // want to search nested scopes. The enum is passed so the callers are
+  // unambiguous about nested scope handling. This can be added if needed.
+  bool HasValues(SearchNested search_nested) const;
+
+  // Returns NULL if there's no such value.
+  //
+  // counts_as_used should be set if the variable is being read in a way that
+  // should count for unused variable checking.
+  //
+  // found_in_scope is set to the scope that contains the definition of the
+  // ident. If the value was provided programmatically (like host_cpu),
+  // found_in_scope will be set to null.
+  const Value* GetValue(const std::string_view& ident, bool counts_as_used);
+  const Value* GetValue(const std::string_view& ident) const;
+  const Value* GetValueWithScope(const std::string_view& ident,
+                                 const Scope** found_in_scope) const;
+  const Value* GetValueWithScope(const std::string_view& ident,
+                                 bool counts_as_used,
+                                 const Scope** found_in_scope);
+
+  // Returns the requested value as a mutable one if possible. If the value
+  // is not found in a mutable scope, then returns null. Note that the value
+  // could still exist in a const scope, so GetValue() could still return
+  // non-null in this case.
+  //
+  // Say you have a local scope that then refers to the const root scope from
+  // the master build config. You can't change the values from the master
+  // build config (it's read-only so it can be read from multiple threads
+  // without locking). Read-only operations would work on values from the root
+  // scope, but write operations would only work on values in the derived
+  // scope(s).
+  //
+  // Be careful when calling this. It's not normally correct to modify values,
+  // but you should instead do a new Set each time.
+  //
+  // Consider this code:
+  //   a = 5
+  //    {
+  //       a = 6
+  //    }
+  // The 6 should get set on the nested scope rather than modify the value
+  // in the outer one.
+  Value* GetMutableValue(const std::string_view& ident,
+                         SearchNested search_mode,
+                         bool counts_as_used);
+
+  // Returns the std::string_view used to identify the value. This string piece
+  // will have the same contents as "ident" passed in, but may point to a
+  // different underlying buffer. This is useful because this std::string_view
+  // is static and won't be deleted for the life of the program, so it can be
+  // used as keys in places that may outlive a temporary. It will return an
+  // empty string for programmatic and nonexistent values.
+  std::string_view GetStorageKey(const std::string_view& ident) const;
+
+  // The set_node indicates the statement that caused the set, for displaying
+  // errors later. Returns a pointer to the value in the current scope (a copy
+  // is made for storage).
+  Value* SetValue(const std::string_view& ident,
+                  Value v,
+                  const ParseNode* set_node);
+
+  // Removes the value with the given identifier if it exists on the current
+  // scope. This does not search recursive scopes. Does nothing if not found.
+  void RemoveIdentifier(const std::string_view& ident);
+
+  // Removes from this scope all identifiers and templates that are considered
+  // private.
+  void RemovePrivateIdentifiers();
+
+  // Templates associated with this scope. A template can only be set once, so
+  // AddTemplate will fail and return false if a rule with that name already
+  // exists. GetTemplate returns NULL if the rule doesn't exist, and it will
+  // check all containing scoped rescursively.
+  bool AddTemplate(const std::string& name, const Template* templ);
+  const Template* GetTemplate(const std::string& name) const;
+
+  // Marks the given identifier as (un)used in the current scope.
+  void MarkUsed(const std::string_view& ident);
+  void MarkAllUsed();
+  void MarkAllUsed(const std::set<std::string>& excluded_values);
+  void MarkUnused(const std::string_view& ident);
+
+  // Checks to see if the scope has a var set that hasn't been used. This is
+  // called before replacing the var with a different one. It does not check
+  // containing scopes.
+  //
+  // If the identifier is present but hasnn't been used, return true.
+  bool IsSetButUnused(const std::string_view& ident) const;
+
+  // Checks the scope to see if any values were set but not used, and fills in
+  // the error and returns false if they were.
+  bool CheckForUnusedVars(Err* err) const;
+
+  // Returns all values set in the current scope, without going to the parent
+  // scopes.
+  void GetCurrentScopeValues(KeyValueMap* output) const;
+
+  // Returns true if the values in the current scope are the same as all
+  // values in the given scope, without going to the parent scopes. Returns
+  // false if not.
+  bool CheckCurrentScopeValuesEqual(const Scope* other) const;
+
+  // Copies this scope's values into the destination. Values from the
+  // containing scope(s) (normally shadowed into the current one) will not be
+  // copied, neither will the reference to the containing scope (this is why
+  // it's "non-recursive").
+  //
+  // This is used in different contexts. When generating the error, the given
+  // parse node will be blamed, and the given desc will be used to describe
+  // the operation that doesn't support doing this. For example, desc_for_err
+  // would be "import" when doing an import, and the error string would say
+  // something like "The import contains...".
+  bool NonRecursiveMergeTo(Scope* dest,
+                           const MergeOptions& options,
+                           const ParseNode* node_for_err,
+                           const char* desc_for_err,
+                           Err* err) const;
+
+  // Constructs a scope that is a copy of the current one. Nested scopes will
+  // be collapsed until we reach a const containing scope. Private values will
+  // be included. The resulting closure will reference the const containing
+  // scope as its containing scope (since we assume the const scope won't
+  // change, we don't have to copy its values).
+  std::unique_ptr<Scope> MakeClosure() const;
+
+  // Makes an empty scope with the given name. Overwrites any existing one.
+  Scope* MakeTargetDefaults(const std::string& target_type);
+
+  // Gets the scope associated with the given target name, or null if it hasn't
+  // been set.
+  const Scope* GetTargetDefaults(const std::string& target_type) const;
+
+  // Indicates if we're currently processing the build configuration file.
+  // This is true when processing the config file for any toolchain.
+  //
+  // To set or clear the flag, it must currently be in the opposite state in
+  // the current scope. Note that querying the state of the flag recursively
+  // checks all containing scopes until it reaches the top or finds the flag
+  // set.
+  void SetProcessingBuildConfig();
+  void ClearProcessingBuildConfig();
+  bool IsProcessingBuildConfig() const;
+
+  // Indicates if we're currently processing an import file.
+  //
+  // See SetProcessingBaseConfig for how flags work.
+  void SetProcessingImport();
+  void ClearProcessingImport();
+  bool IsProcessingImport() const;
+
+  // The source directory associated with this scope. This will check embedded
+  // scopes until it finds a nonempty source directory. This will default to
+  // an empty dir if no containing scope has a source dir set.
+  const SourceDir& GetSourceDir() const;
+  void set_source_dir(const SourceDir& d) { source_dir_ = d; }
+
+  // Set of files that may affect the execution of this scope. Note that this
+  // set is constructed conservatively, meaning that every file that can
+  // potentially affect this scope is included, but not necessarily every change
+  // to these files will affect this scope.
+  const SourceFileSet& build_dependency_files() const {
+    return build_dependency_files_;
+  }
+  void AddBuildDependencyFile(const SourceFile& build_dependency_file);
+  void AddBuildDependencyFiles(const SourceFileSet& build_dependency_files);
+
+  // The item collector is where Items (Targets, Configs, etc.) go that have
+  // been defined. If a scope can generate items, this non-owning pointer will
+  // point to the storage for such items. The creator of this scope will be
+  // responsible for setting up the collector and then dealing with the
+  // collected items once execution of the context is complete.
+  //
+  // The items in a scope are collected as we go and then dispatched at the end
+  // of execution of a scope so that we can query the previously-generated
+  // targets (like getting the outputs).
+  //
+  // This can be null if the current scope can not generate items (like for
+  // imports and such).
+  //
+  // When retrieving the collector, the non-const scopes are recursively
+  // queried. The collector is not copied for closures, etc.
+  void set_item_collector(ItemVector* collector) {
+    item_collector_ = collector;
+  }
+  ItemVector* GetItemCollector();
+
+  // Properties are opaque pointers that code can use to set state on a Scope
+  // that it can retrieve later.
+  //
+  // The key should be a pointer to some use-case-specific object (to avoid
+  // collisions, otherwise it doesn't matter). Memory management is up to the
+  // setter. Setting the value to NULL will delete the property.
+  //
+  // Getting a property recursively searches all scopes, and the optional
+  // |found_on_scope| variable will be filled with the actual scope containing
+  // the key (if the pointer is non-NULL).
+  void SetProperty(const void* key, void* value);
+  void* GetProperty(const void* key, const Scope** found_on_scope) const;
+
+ private:
+  friend class ProgrammaticProvider;
+
+  struct Record {
+    Record() : used(false) {}
+    explicit Record(const Value& v) : used(false), value(v) {}
+
+    bool used;  // Set to true when the variable is used.
+    Value value;
+  };
+
+  using RecordMap = std::unordered_map<std::string_view, Record>;
+
+  void AddProvider(ProgrammaticProvider* p);
+  void RemoveProvider(ProgrammaticProvider* p);
+
+  // Returns true if the two RecordMaps contain the same values (the origins
+  // of the values may be different).
+  static bool RecordMapValuesEqual(const RecordMap& a, const RecordMap& b);
+
+  // Scopes can have no containing scope (both null), a mutable containing
+  // scope, or a const containing scope. The reason is that when we're doing
+  // a new target, we want to refer to the base_config scope which will be read
+  // by multiple threads at the same time, so we REALLY want it to be const.
+  // When you just do a nested {}, however, we sometimes want to be able to
+  // change things (especially marking unused vars).
+  const Scope* const_containing_;
+  Scope* mutable_containing_;
+
+  const Settings* settings_;
+
+  // Bits set for different modes. See the flag definitions in the .cc file
+  // for more.
+  unsigned mode_flags_;
+
+  RecordMap values_;
+
+  // Note that this can't use string pieces since the names are constructed from
+  // Values which might be deallocated before this goes out of scope.
+  using NamedScopeMap = std::unordered_map<std::string, std::unique_ptr<Scope>>;
+  NamedScopeMap target_defaults_;
+
+  // Owning pointers, must be deleted.
+  using TemplateMap = std::map<std::string, scoped_refptr<const Template>>;
+  TemplateMap templates_;
+
+  ItemVector* item_collector_;
+
+  // Opaque pointers. See SetProperty() above.
+  using PropertyMap = std::map<const void*, void*>;
+  PropertyMap properties_;
+
+  using ProviderSet = std::set<ProgrammaticProvider*>;
+  ProviderSet programmatic_providers_;
+
+  SourceDir source_dir_;
+
+  SourceFileSet build_dependency_files_;
+
+  DISALLOW_COPY_AND_ASSIGN(Scope);
+};
+
+#endif  // TOOLS_GN_SCOPE_H_
diff --git a/src/gn/scope_per_file_provider.cc b/src/gn/scope_per_file_provider.cc
new file mode 100644 (file)
index 0000000..3110a8f
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scope_per_file_provider.h"
+
+#include <memory>
+
+#include "gn/filesystem_utils.h"
+#include "gn/settings.h"
+#include "gn/source_file.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+
+#include "last_commit_position.h"
+
+ScopePerFileProvider::ScopePerFileProvider(Scope* scope, bool allow_target_vars)
+    : ProgrammaticProvider(scope), allow_target_vars_(allow_target_vars) {}
+
+ScopePerFileProvider::~ScopePerFileProvider() = default;
+
+const Value* ScopePerFileProvider::GetProgrammaticValue(
+    const std::string_view& ident) {
+  if (ident == variables::kCurrentToolchain)
+    return GetCurrentToolchain();
+  if (ident == variables::kDefaultToolchain)
+    return GetDefaultToolchain();
+  if (ident == variables::kGnVersion)
+    return GetGnVersion();
+  if (ident == variables::kPythonPath)
+    return GetPythonPath();
+
+  if (ident == variables::kRootBuildDir)
+    return GetRootBuildDir();
+  if (ident == variables::kRootGenDir)
+    return GetRootGenDir();
+  if (ident == variables::kRootOutDir)
+    return GetRootOutDir();
+
+  if (allow_target_vars_) {
+    if (ident == variables::kTargetGenDir)
+      return GetTargetGenDir();
+    if (ident == variables::kTargetOutDir)
+      return GetTargetOutDir();
+  }
+  return nullptr;
+}
+
+const Value* ScopePerFileProvider::GetCurrentToolchain() {
+  if (!current_toolchain_) {
+    current_toolchain_ = std::make_unique<Value>(
+        nullptr,
+        scope_->settings()->toolchain_label().GetUserVisibleName(false));
+  }
+  return current_toolchain_.get();
+}
+
+const Value* ScopePerFileProvider::GetDefaultToolchain() {
+  if (!default_toolchain_) {
+    default_toolchain_ = std::make_unique<Value>(
+        nullptr,
+        scope_->settings()->default_toolchain_label().GetUserVisibleName(
+            false));
+  }
+  return default_toolchain_.get();
+}
+
+const Value* ScopePerFileProvider::GetGnVersion() {
+  if (!gn_version_) {
+    gn_version_ = std::make_unique<Value>(
+        nullptr, static_cast<int64_t>(LAST_COMMIT_POSITION_NUM));
+  }
+  return gn_version_.get();
+}
+
+const Value* ScopePerFileProvider::GetPythonPath() {
+  if (!python_path_) {
+    python_path_ = std::make_unique<Value>(
+        nullptr,
+        FilePathToUTF8(scope_->settings()->build_settings()->python_path()));
+  }
+  return python_path_.get();
+}
+
+const Value* ScopePerFileProvider::GetRootBuildDir() {
+  if (!root_build_dir_) {
+    root_build_dir_ = std::make_unique<Value>(
+        nullptr, DirectoryWithNoLastSlash(
+                     scope_->settings()->build_settings()->build_dir()));
+  }
+  return root_build_dir_.get();
+}
+
+const Value* ScopePerFileProvider::GetRootGenDir() {
+  if (!root_gen_dir_) {
+    root_gen_dir_ = std::make_unique<Value>(
+        nullptr, DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+                     BuildDirContext(scope_), BuildDirType::GEN)));
+  }
+  return root_gen_dir_.get();
+}
+
+const Value* ScopePerFileProvider::GetRootOutDir() {
+  if (!root_out_dir_) {
+    root_out_dir_ = std::make_unique<Value>(
+        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
+                     scope_, BuildDirType::TOOLCHAIN_ROOT)));
+  }
+  return root_out_dir_.get();
+}
+
+const Value* ScopePerFileProvider::GetTargetGenDir() {
+  if (!target_gen_dir_) {
+    target_gen_dir_ = std::make_unique<Value>(
+        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
+                     scope_, BuildDirType::GEN)));
+  }
+  return target_gen_dir_.get();
+}
+
+const Value* ScopePerFileProvider::GetTargetOutDir() {
+  if (!target_out_dir_) {
+    target_out_dir_ = std::make_unique<Value>(
+        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
+                     scope_, BuildDirType::OBJ)));
+  }
+  return target_out_dir_.get();
+}
diff --git a/src/gn/scope_per_file_provider.h b/src/gn/scope_per_file_provider.h
new file mode 100644 (file)
index 0000000..228bac3
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_
+#define TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "gn/scope.h"
+
+// ProgrammaticProvider for a scope to provide it with per-file built-in
+// variable support.
+class ScopePerFileProvider : public Scope::ProgrammaticProvider {
+ public:
+  // allow_target_vars allows the target-related variables to get resolved.
+  // When allow_target_vars is unset, the target-related values will be
+  // undefined to GN script.
+  ScopePerFileProvider(Scope* scope, bool allow_target_vars);
+  ~ScopePerFileProvider() override;
+
+  // ProgrammaticProvider implementation.
+  const Value* GetProgrammaticValue(const std::string_view& ident) override;
+
+ private:
+  const Value* GetCurrentToolchain();
+  const Value* GetDefaultToolchain();
+  const Value* GetGnVersion();
+  const Value* GetPythonPath();
+  const Value* GetRootBuildDir();
+  const Value* GetRootGenDir();
+  const Value* GetRootOutDir();
+  const Value* GetTargetGenDir();
+  const Value* GetTargetOutDir();
+
+  bool allow_target_vars_;
+
+  // All values are lazily created.
+  std::unique_ptr<Value> current_toolchain_;
+  std::unique_ptr<Value> default_toolchain_;
+  std::unique_ptr<Value> gn_version_;
+  std::unique_ptr<Value> python_path_;
+  std::unique_ptr<Value> root_build_dir_;
+  std::unique_ptr<Value> root_gen_dir_;
+  std::unique_ptr<Value> root_out_dir_;
+  std::unique_ptr<Value> target_gen_dir_;
+  std::unique_ptr<Value> target_out_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopePerFileProvider);
+};
+
+#endif  // TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_
diff --git a/src/gn/scope_per_file_provider_unittest.cc b/src/gn/scope_per_file_provider_unittest.cc
new file mode 100644 (file)
index 0000000..8e38903
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scope_per_file_provider.h"
+#include "gn/build_settings.h"
+#include "gn/settings.h"
+#include "gn/test_with_scope.h"
+#include "gn/toolchain.h"
+#include "gn/variables.h"
+#include "util/test/test.h"
+
+TEST(ScopePerFileProvider, Expected) {
+  TestWithScope test;
+
+// Prevent horrible wrapping of calls below.
+#define GPV(val) provider.GetProgrammaticValue(val)->string_value()
+
+  // Test the default toolchain.
+  {
+    Scope scope(test.settings());
+    scope.set_source_dir(SourceDir("//source/"));
+    ScopePerFileProvider provider(&scope, true);
+
+    EXPECT_EQ("//toolchain:default", GPV(variables::kCurrentToolchain));
+    // TODO(brettw) this test harness does not set up the Toolchain manager
+    // which is the source of this value, so we can't test this yet.
+    // EXPECT_EQ("//toolchain:default",    GPV(variables::kDefaultToolchain));
+    EXPECT_EQ("//out/Debug", GPV(variables::kRootBuildDir));
+    EXPECT_EQ("//out/Debug/gen", GPV(variables::kRootGenDir));
+    EXPECT_EQ("//out/Debug", GPV(variables::kRootOutDir));
+    EXPECT_EQ("//out/Debug/gen/source", GPV(variables::kTargetGenDir));
+    EXPECT_EQ("//out/Debug/obj/source", GPV(variables::kTargetOutDir));
+
+    EXPECT_GE(provider.GetProgrammaticValue(variables::kGnVersion)->int_value(),
+              0);
+  }
+
+  // Test some with an alternate toolchain.
+  {
+    Settings settings(test.build_settings(), "tc/");
+    Toolchain toolchain(&settings, Label(SourceDir("//toolchain/"), "tc"));
+    settings.set_toolchain_label(toolchain.label());
+
+    Scope scope(&settings);
+    scope.set_source_dir(SourceDir("//source/"));
+    ScopePerFileProvider provider(&scope, true);
+
+    EXPECT_EQ("//toolchain:tc", GPV(variables::kCurrentToolchain));
+    // See above.
+    // EXPECT_EQ("//toolchain:default", GPV(variables::kDefaultToolchain));
+    EXPECT_EQ("//out/Debug", GPV(variables::kRootBuildDir));
+    EXPECT_EQ("//out/Debug/tc/gen", GPV(variables::kRootGenDir));
+    EXPECT_EQ("//out/Debug/tc", GPV(variables::kRootOutDir));
+    EXPECT_EQ("//out/Debug/tc/gen/source", GPV(variables::kTargetGenDir));
+    EXPECT_EQ("//out/Debug/tc/obj/source", GPV(variables::kTargetOutDir));
+  }
+}
diff --git a/src/gn/scope_unittest.cc b/src/gn/scope_unittest.cc
new file mode 100644 (file)
index 0000000..af39a9d
--- /dev/null
@@ -0,0 +1,335 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/scope.h"
+
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/source_file.h"
+#include "gn/template.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool HasStringValueEqualTo(const Scope* scope,
+                           const char* name,
+                           const char* expected_value) {
+  const Value* value = scope->GetValue(name);
+  if (!value)
+    return false;
+  if (value->type() != Value::STRING)
+    return false;
+  return value->string_value() == expected_value;
+}
+
+bool ContainsBuildDependencyFile(const Scope* scope,
+                                 const SourceFile& source_file) {
+  const auto& build_dependency_files = scope->build_dependency_files();
+  return build_dependency_files.end() !=
+         build_dependency_files.find(source_file);
+}
+
+}  // namespace
+
+TEST(Scope, InheritBuildDependencyFilesFromParent) {
+  TestWithScope setup;
+  SourceFile source_file = SourceFile("//a/BUILD.gn");
+  setup.scope()->AddBuildDependencyFile(source_file);
+
+  Scope new_scope(setup.scope());
+  EXPECT_EQ(1U, new_scope.build_dependency_files().size());
+  EXPECT_TRUE(ContainsBuildDependencyFile(&new_scope, source_file));
+}
+
+TEST(Scope, NonRecursiveMergeTo) {
+  TestWithScope setup;
+
+  // Make a pretend parse node with proper tracking that we can blame for the
+  // given value.
+  InputFile input_file(SourceFile("//foo"));
+  Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
+                         "\"hello\"");
+  LiteralNode assignment;
+  assignment.set_value(assignment_token);
+
+  // Add some values to the scope.
+  Value old_value(&assignment, "hello");
+  setup.scope()->SetValue("v", old_value, &assignment);
+  std::string_view private_var_name("_private");
+  setup.scope()->SetValue(private_var_name, old_value, &assignment);
+
+  // Add some templates to the scope.
+  FunctionCallNode templ_definition;
+  scoped_refptr<Template> templ(new Template(setup.scope(), &templ_definition));
+  setup.scope()->AddTemplate("templ", templ.get());
+  scoped_refptr<Template> private_templ(
+      new Template(setup.scope(), &templ_definition));
+  setup.scope()->AddTemplate("_templ", private_templ.get());
+
+  // Detect collisions of values' values.
+  {
+    Scope new_scope(setup.settings());
+    Value new_value(&assignment, "goodbye");
+    new_scope.SetValue("v", new_value, &assignment);
+
+    Err err;
+    EXPECT_FALSE(setup.scope()->NonRecursiveMergeTo(
+        &new_scope, Scope::MergeOptions(), &assignment, "error", &err));
+    EXPECT_TRUE(err.has_error());
+  }
+
+  // Template name collisions.
+  {
+    Scope new_scope(setup.settings());
+
+    scoped_refptr<Template> new_templ(
+        new Template(&new_scope, &templ_definition));
+    new_scope.AddTemplate("templ", new_templ.get());
+
+    Err err;
+    EXPECT_FALSE(setup.scope()->NonRecursiveMergeTo(
+        &new_scope, Scope::MergeOptions(), &assignment, "error", &err));
+    EXPECT_TRUE(err.has_error());
+  }
+
+  // The clobber flag should just overwrite colliding values.
+  {
+    Scope new_scope(setup.settings());
+    Value new_value(&assignment, "goodbye");
+    new_scope.SetValue("v", new_value, &assignment);
+
+    Err err;
+    Scope::MergeOptions options;
+    options.clobber_existing = true;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
+                                                   &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+
+    const Value* found_value = new_scope.GetValue("v");
+    ASSERT_TRUE(found_value);
+    EXPECT_TRUE(old_value == *found_value);
+  }
+
+  // Clobber flag for templates.
+  {
+    Scope new_scope(setup.settings());
+
+    scoped_refptr<Template> new_templ(
+        new Template(&new_scope, &templ_definition));
+    new_scope.AddTemplate("templ", new_templ.get());
+    Scope::MergeOptions options;
+    options.clobber_existing = true;
+
+    Err err;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
+                                                   &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+
+    const Template* found_value = new_scope.GetTemplate("templ");
+    ASSERT_TRUE(found_value);
+    EXPECT_TRUE(templ.get() == found_value);
+  }
+
+  // Don't flag values that technically collide but have the same value.
+  {
+    Scope new_scope(setup.settings());
+    Value new_value(&assignment, "hello");
+    new_scope.SetValue("v", new_value, &assignment);
+
+    Err err;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
+        &new_scope, Scope::MergeOptions(), &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+  }
+
+  // Templates that technically collide but are the same.
+  {
+    Scope new_scope(setup.settings());
+
+    scoped_refptr<Template> new_templ(
+        new Template(&new_scope, &templ_definition));
+    new_scope.AddTemplate("templ", templ.get());
+
+    Err err;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
+        &new_scope, Scope::MergeOptions(), &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+  }
+
+  // Copy private values and templates.
+  {
+    Scope new_scope(setup.settings());
+
+    Err err;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
+        &new_scope, Scope::MergeOptions(), &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+    EXPECT_TRUE(new_scope.GetValue(private_var_name));
+    EXPECT_TRUE(new_scope.GetTemplate("_templ"));
+  }
+
+  // Skip private values and templates.
+  {
+    Scope new_scope(setup.settings());
+
+    Err err;
+    Scope::MergeOptions options;
+    options.skip_private_vars = true;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
+                                                   &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+    EXPECT_FALSE(new_scope.GetValue(private_var_name));
+    EXPECT_FALSE(new_scope.GetTemplate("_templ"));
+  }
+
+  // Don't mark used.
+  {
+    Scope new_scope(setup.settings());
+
+    Err err;
+    Scope::MergeOptions options;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
+                                                   &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+    EXPECT_FALSE(new_scope.CheckForUnusedVars(&err));
+    EXPECT_TRUE(err.has_error());
+  }
+
+  // Mark dest used.
+  {
+    Scope new_scope(setup.settings());
+
+    Err err;
+    Scope::MergeOptions options;
+    options.mark_dest_used = true;
+    EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
+                                                   &assignment, "error", &err));
+    EXPECT_FALSE(err.has_error());
+    EXPECT_TRUE(new_scope.CheckForUnusedVars(&err));
+    EXPECT_FALSE(err.has_error());
+  }
+
+  // Build dependency files are merged.
+  {
+    Scope from_scope(setup.settings());
+    SourceFile source_file = SourceFile("//a/BUILD.gn");
+    from_scope.AddBuildDependencyFile(source_file);
+
+    Scope to_scope(setup.settings());
+    EXPECT_FALSE(ContainsBuildDependencyFile(&to_scope, source_file));
+
+    Scope::MergeOptions options;
+    Err err;
+    EXPECT_TRUE(from_scope.NonRecursiveMergeTo(&to_scope, options, &assignment,
+                                               "error", &err));
+    EXPECT_FALSE(err.has_error());
+    EXPECT_EQ(1U, to_scope.build_dependency_files().size());
+    EXPECT_TRUE(ContainsBuildDependencyFile(&to_scope, source_file));
+  }
+}
+
+TEST(Scope, MakeClosure) {
+  // Create 3 nested scopes [const root from setup] <- nested1 <- nested2.
+  TestWithScope setup;
+
+  // Make a pretend parse node with proper tracking that we can blame for the
+  // given value.
+  InputFile input_file(SourceFile("//foo"));
+  Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
+                         "\"hello\"");
+  LiteralNode assignment;
+  assignment.set_value(assignment_token);
+  setup.scope()->SetValue("on_root", Value(&assignment, "on_root"),
+                          &assignment);
+
+  // Root scope should be const from the nested caller's perspective.
+  Scope nested1(static_cast<const Scope*>(setup.scope()));
+  nested1.SetValue("on_one", Value(&assignment, "on_one"), &assignment);
+
+  Scope nested2(&nested1);
+  nested2.SetValue("on_one", Value(&assignment, "on_two"), &assignment);
+  nested2.SetValue("on_two", Value(&assignment, "on_two2"), &assignment);
+
+  // Making a closure from the root scope.
+  std::unique_ptr<Scope> result = setup.scope()->MakeClosure();
+  EXPECT_FALSE(result->containing());        // Should have no containing scope.
+  EXPECT_TRUE(result->GetValue("on_root"));  // Value should be copied.
+
+  // Making a closure from the second nested scope.
+  result = nested2.MakeClosure();
+  EXPECT_EQ(setup.scope(),
+            result->containing());  // Containing scope should be the root.
+  EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_root", "on_root"));
+  EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_one", "on_two"));
+  EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_two", "on_two2"));
+}
+
+TEST(Scope, GetMutableValue) {
+  TestWithScope setup;
+
+  // Make a pretend parse node with proper tracking that we can blame for the
+  // given value.
+  InputFile input_file(SourceFile("//foo"));
+  Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
+                         "\"hello\"");
+  LiteralNode assignment;
+  assignment.set_value(assignment_token);
+
+  const char kOnConst[] = "on_const";
+  const char kOnMutable1[] = "on_mutable1";
+  const char kOnMutable2[] = "on_mutable2";
+
+  Value value(&assignment, "hello");
+
+  // Create a root scope with one value.
+  Scope root_scope(setup.settings());
+  root_scope.SetValue(kOnConst, value, &assignment);
+
+  // Create a first nested scope with a different value.
+  const Scope* const_root_scope = &root_scope;
+  Scope mutable_scope1(const_root_scope);
+  mutable_scope1.SetValue(kOnMutable1, value, &assignment);
+
+  // Create a second nested scope with a different value.
+  Scope mutable_scope2(&mutable_scope1);
+  mutable_scope2.SetValue(kOnMutable2, value, &assignment);
+
+  // Check getting root scope values.
+  EXPECT_TRUE(mutable_scope2.GetValue(kOnConst, true));
+  EXPECT_FALSE(
+      mutable_scope2.GetMutableValue(kOnConst, Scope::SEARCH_NESTED, true));
+
+  // Test reading a value from scope 1.
+  Value* mutable1_result =
+      mutable_scope2.GetMutableValue(kOnMutable1, Scope::SEARCH_NESTED, false);
+  ASSERT_TRUE(mutable1_result);
+  EXPECT_TRUE(*mutable1_result == value);
+
+  // Make sure CheckForUnusedVars works on scope1 (we didn't mark the value as
+  // used in the previous step).
+  Err err;
+  EXPECT_FALSE(mutable_scope1.CheckForUnusedVars(&err));
+  mutable1_result =
+      mutable_scope2.GetMutableValue(kOnMutable1, Scope::SEARCH_NESTED, true);
+  EXPECT_TRUE(mutable1_result);
+  err = Err();
+  EXPECT_TRUE(mutable_scope1.CheckForUnusedVars(&err));
+
+  // Test reading a value from scope 2.
+  Value* mutable2_result =
+      mutable_scope2.GetMutableValue(kOnMutable2, Scope::SEARCH_NESTED, true);
+  ASSERT_TRUE(mutable2_result);
+  EXPECT_TRUE(*mutable2_result == value);
+}
+
+TEST(Scope, RemovePrivateIdentifiers) {
+  TestWithScope setup;
+  setup.scope()->SetValue("a", Value(nullptr, true), nullptr);
+  setup.scope()->SetValue("_b", Value(nullptr, true), nullptr);
+
+  setup.scope()->RemovePrivateIdentifiers();
+  EXPECT_TRUE(setup.scope()->GetValue("a"));
+  EXPECT_FALSE(setup.scope()->GetValue("_b"));
+}
diff --git a/src/gn/settings.cc b/src/gn/settings.cc
new file mode 100644 (file)
index 0000000..154e0af
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/settings.h"
+
+#include "base/logging.h"
+#include "gn/filesystem_utils.h"
+#include "util/build_config.h"
+
+Settings::Settings(const BuildSettings* build_settings,
+                   const std::string& output_subdir_name)
+    : build_settings_(build_settings), base_config_(this) {
+  if (output_subdir_name.empty()) {
+    toolchain_output_dir_ = build_settings->build_dir();
+  } else {
+    // We guarantee this ends in a slash.
+    DCHECK(output_subdir_name[output_subdir_name.size() - 1] == '/');
+    toolchain_output_subdir_.value().append(output_subdir_name);
+
+    DCHECK(!build_settings->build_dir().is_null());
+    toolchain_output_dir_ = SourceDir(build_settings->build_dir().value() +
+                                      toolchain_output_subdir_.value());
+  }
+  // The output dir will be null in some tests and when invoked to parsed
+  // one-off data without doing generation.
+  if (!toolchain_output_dir_.is_null())
+    toolchain_gen_dir_ = SourceDir(toolchain_output_dir_.value() + "gen/");
+}
diff --git a/src/gn/settings.h b/src/gn/settings.h
new file mode 100644 (file)
index 0000000..755513a
--- /dev/null
@@ -0,0 +1,116 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SETTINGS_H_
+#define TOOLS_GN_SETTINGS_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gn/import_manager.h"
+#include "gn/output_file.h"
+#include "gn/scope.h"
+#include "gn/source_dir.h"
+#include "gn/toolchain.h"
+
+class BuildSettings;
+
+// Holds the settings for one toolchain invocation. There will be one
+// Settings object for each toolchain type, each referring to the same
+// BuildSettings object for shared stuff.
+//
+// The Settings object is const once it is constructed, which allows us to
+// use it from multiple threads during target generation without locking (which
+// is important, because it gets used a lot).
+//
+// The Toolchain object holds the set of stuff that is set by the toolchain
+// declaration, which obviously needs to be set later when we actually parse
+// the file with the toolchain declaration in it.
+class Settings {
+ public:
+  // Constructs a toolchain settings.
+  //
+  // The output_subdir_name is the name we should use for the subdirectory in
+  // the build output directory for this toolchain's outputs. The default
+  // toolchain would use an empty string (it goes in the root build dir).
+  // Otherwise, it must end in a slash.
+  Settings(const BuildSettings* build_settings,
+           const std::string& output_subdir_name);
+
+  const BuildSettings* build_settings() const { return build_settings_; }
+
+  // The actual Toolchain object pointer is not available on the settings
+  // object because it might not be resolved yet. Code running after the
+  // load is complete can ask the Builder for the Toolchain corresponding to
+  // this label.
+  const Label& toolchain_label() const { return toolchain_label_; }
+  void set_toolchain_label(const Label& l) { toolchain_label_ = l; }
+
+  const Label& default_toolchain_label() const {
+    return default_toolchain_label_;
+  }
+  void set_default_toolchain_label(const Label& default_label) {
+    default_toolchain_label_ = default_label;
+  }
+
+  // Indicates if this corresponds to the default toolchain.
+  bool is_default() const {
+    return toolchain_label_ == default_toolchain_label_;
+  }
+
+  const OutputFile& toolchain_output_subdir() const {
+    return toolchain_output_subdir_;
+  }
+  const SourceDir& toolchain_output_dir() const {
+    return toolchain_output_dir_;
+  }
+
+  // Directory for generated files.
+  const SourceDir& toolchain_gen_dir() const { return toolchain_gen_dir_; }
+
+  // The import manager caches the result of executing imported files in the
+  // context of a given settings object.
+  //
+  // See the ItemTree getter in GlobalSettings for why this doesn't return a
+  // const pointer.
+  ImportManager& import_manager() const { return import_manager_; }
+
+  const Scope* base_config() const { return &base_config_; }
+  Scope* base_config() { return &base_config_; }
+
+  // Set to true when every target we encounter should be generated. False
+  // means that only targets that have a dependency from (directly or
+  // indirectly) some magic root node are actually generated. See the comments
+  // on ItemTree for more.
+  bool greedy_target_generation() const { return greedy_target_generation_; }
+  void set_greedy_target_generation(bool gtg) {
+    greedy_target_generation_ = gtg;
+  }
+
+ private:
+  const BuildSettings* build_settings_;
+
+  Label toolchain_label_;
+  Label default_toolchain_label_;
+
+  mutable ImportManager import_manager_;
+
+  // The subdirectory inside the build output for this toolchain. For the
+  // default toolchain, this will be empty (since the default toolchain's
+  // output directory is the same as the build directory). When nonempty, this
+  // is guaranteed to end in a slash.
+  OutputFile toolchain_output_subdir_;
+
+  // Full source file path to the toolchain output directory.
+  SourceDir toolchain_output_dir_;
+
+  SourceDir toolchain_gen_dir_;
+
+  Scope base_config_;
+
+  bool greedy_target_generation_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(Settings);
+};
+
+#endif  // TOOLS_GN_SETTINGS_H_
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
new file mode 100644 (file)
index 0000000..b6fdee2
--- /dev/null
@@ -0,0 +1,1028 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/setup.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/command_format.h"
+#include "gn/commands.h"
+#include "gn/exec_process.h"
+#include "gn/filesystem_utils.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/parser.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/standard_out.h"
+#include "gn/switches.h"
+#include "gn/tokenizer.h"
+#include "gn/trace.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/scoped_process_information.h"
+#include "base/win/win_util.h"
+#endif
+
+const char kDotfile_Help[] =
+    R"(.gn file
+
+  When gn starts, it will search the current directory and parent directories
+  for a file called ".gn". This indicates the source root. You can override
+  this detection by using the --root command-line argument
+
+  The .gn file in the source root will be executed. The syntax is the same as a
+  buildfile, but with very limited build setup-specific meaning.
+
+  If you specify --root, by default GN will look for the file .gn in that
+  directory. If you want to specify a different file, you can additionally pass
+  --dotfile:
+
+    gn gen out/Debug --root=/home/build --dotfile=/home/my_gn_file.gn
+
+Variables
+
+  arg_file_template [optional]
+      Path to a file containing the text that should be used as the default
+      args.gn content when you run `gn args`.
+
+  buildconfig [required]
+      Path to the build config file. This file will be used to set up the
+      build file execution environment for each toolchain.
+
+  check_targets [optional]
+      A list of labels and label patterns that should be checked when running
+      "gn check" or "gn gen --check". If neither check_targets or
+      no_check_targets (see below) is specified, all targets will be checked.
+      It is an error to specify both check_targets and no_check_targets. If it
+      is the empty list, no targets will be checked. To bypass this list,
+      request an explicit check of targets, like "//*".
+
+      The format of this list is identical to that of "visibility" so see "gn
+      help visibility" for examples.
+
+  no_check_targets [optional]
+      A list of labels and label patterns that should *not* be checked when
+      running "gn check" or "gn gen --check". All other targets will be checked.
+      If neither check_targets (see above) or no_check_targets is specified, all
+      targets will be checked. It is an error to specify both check_targets and
+      no_check_targets.
+
+      The format of this list is identical to that of "visibility" so see "gn
+      help visibility" for examples.
+
+  check_system_includes [optional]
+      Boolean to control whether system style includes are checked by default
+      when running "gn check" or "gn gen --check".  System style includes are
+      includes that use angle brackets <> instead of double quotes "". If this
+      setting is omitted or set to false, these includes will be ignored by
+      default. They can be checked explicitly by running
+      "gn check --check-system" or "gn gen --check=system"
+
+  exec_script_whitelist [optional]
+      A list of .gn/.gni files (not labels) that have permission to call the
+      exec_script function. If this list is defined, calls to exec_script will
+      be checked against this list and GN will fail if the current file isn't
+      in the list.
+
+      This is to allow the use of exec_script to be restricted since is easy to
+      use inappropriately. Wildcards are not supported. Files in the
+      secondary_source tree (if defined) should be referenced by ignoring the
+      secondary tree and naming them as if they are in the main tree.
+
+      If unspecified, the ability to call exec_script is unrestricted.
+
+      Example:
+        exec_script_whitelist = [
+          "//base/BUILD.gn",
+          "//build/my_config.gni",
+        ]
+
+  root [optional]
+      Label of the root build target. The GN build will start by loading the
+      build file containing this target name. This defaults to "//:" which will
+      cause the file //BUILD.gn to be loaded. Note that build_file_extension
+      applies to the default case as well.
+
+      The command-line switch --root-target will override this value (see "gn
+      help --root-target").
+
+  script_executable [optional]
+      Path to specific Python executable or other interpreter to use in
+      action targets and exec_script calls. By default GN searches the
+      PATH for Python to execute these scripts.
+
+      If set to the empty string, the path specified in action targets
+      and exec_script calls will be executed directly.
+
+  secondary_source [optional]
+      Label of an alternate directory tree to find input files. When searching
+      for a BUILD.gn file (or the build config file discussed above), the file
+      will first be looked for in the source root. If it's not found, the
+      secondary source root will be checked (which would contain a parallel
+      directory hierarchy).
+
+      This behavior is intended to be used when BUILD.gn files can't be checked
+      in to certain source directories for whatever reason.
+
+      The secondary source root must be inside the main source tree.
+
+  default_args [optional]
+      Scope containing the default overrides for declared arguments. These
+      overrides take precedence over the default values specified in the
+      declare_args() block, but can be overridden using --args or the
+      args.gn file.
+
+      This is intended to be used when subprojects declare arguments with
+      default values that need to be changed for whatever reason.
+
+  build_file_extension [optional]
+      If set to a non-empty string, this is added to the name of all build files
+      to load.
+      GN will look for build files named "BUILD.$build_file_extension.gn".
+      This is intended to be used during migrations or other situations where
+      there are two independent GN builds in the same directories.
+
+  ninja_required_version [optional]
+      When set specifies the minimum required version of Ninja. The default
+      required version is 1.7.2. Specifying a higher version might enable the
+      use of some of newer features that can make the build more efficient.
+
+Example .gn file contents
+
+  buildconfig = "//build/config/BUILDCONFIG.gn"
+
+  check_targets = [
+    "//doom_melon/*",  # Check everything in this subtree.
+    "//tools:mind_controlling_ant",  # Check this specific target.
+  ]
+
+  root = "//:root"
+
+  secondary_source = "//build/config/temporary_buildfiles/"
+
+  default_args = {
+    # Default to release builds for this project.
+    is_debug = false
+    is_component_build = false
+  }
+)";
+
+namespace {
+
+const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn");
+const char kDefaultArgsGn[] =
+    "# Set build arguments here. See `gn help buildargs`.";
+
+base::FilePath FindDotFile(const base::FilePath& current_dir) {
+  base::FilePath try_this_file = current_dir.Append(kGnFile);
+  if (base::PathExists(try_this_file))
+    return try_this_file;
+
+  base::FilePath with_no_slash = current_dir.StripTrailingSeparators();
+  base::FilePath up_one_dir = with_no_slash.DirName();
+  if (up_one_dir == current_dir)
+    return base::FilePath();  // Got to the top.
+
+  return FindDotFile(up_one_dir);
+}
+
+// Called on any thread. Post the item to the builder on the main thread.
+void ItemDefinedCallback(MsgLoop* task_runner,
+                         Builder* builder_call_on_main_thread_only,
+                         std::unique_ptr<Item> item) {
+  DCHECK(item);
+
+  // Increment the work count for the duration of defining the item with the
+  // builder. Otherwise finishing this callback will race finishing loading
+  // files. If there is no other pending work at any point in the middle of
+  // this call completing on the main thread, the 'Complete' function will
+  // be signaled and we'll stop running with an incomplete build.
+  g_scheduler->IncrementWorkCount();
+
+  // Work around issue binding a unique_ptr with std::function by moving into a
+  // shared_ptr.
+  auto item_shared = std::make_shared<std::unique_ptr<Item>>(std::move(item));
+  task_runner->PostTask(
+      [builder_call_on_main_thread_only, item_shared]() mutable {
+        builder_call_on_main_thread_only->ItemDefined(std::move(*item_shared));
+        g_scheduler->DecrementWorkCount();
+      });
+}
+
+void DecrementWorkCount() {
+  g_scheduler->DecrementWorkCount();
+}
+
+#if defined(OS_WIN)
+
+std::u16string SysMultiByteTo16(std::string_view mb) {
+  if (mb.empty())
+    return std::u16string();
+
+  int mb_length = static_cast<int>(mb.length());
+  // Compute the length of the buffer.
+  int charcount = MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, NULL, 0);
+  if (charcount == 0)
+    return std::u16string();
+
+  std::u16string wide;
+  wide.resize(charcount);
+  MultiByteToWideChar(CP_ACP, 0, mb.data(), mb_length, base::ToWCharT(&wide[0]),
+                      charcount);
+
+  return wide;
+}
+
+// Given the path to a batch file that runs Python, extracts the name of the
+// executable actually implementing Python. Generally people write a batch file
+// to put something named "python" on the path, which then just redirects to
+// a python.exe somewhere else. This step decodes that setup. On failure,
+// returns empty path.
+base::FilePath PythonBatToExe(const base::FilePath& bat_path) {
+  // Note exciting double-quoting to allow spaces. The /c switch seems to check
+  // for quotes around the whole thing and then deletes them. If you want to
+  // quote the first argument in addition (to allow for spaces in the Python
+  // path, you need *another* set of quotes around that, likewise, we need
+  // two quotes at the end.
+  std::u16string command = u"cmd.exe /c \"\"";
+  command.append(bat_path.value());
+  command.append(u"\" -c \"import sys; print(sys.executable)\"\"");
+
+  std::string python_path;
+  std::string std_err;
+  int exit_code;
+  base::FilePath cwd;
+  GetCurrentDirectory(&cwd);
+  if (internal::ExecProcess(command, cwd, &python_path, &std_err, &exit_code) &&
+      exit_code == 0 && std_err.empty()) {
+    base::TrimWhitespaceASCII(python_path, base::TRIM_ALL, &python_path);
+
+    // Python uses the system multibyte code page for sys.executable.
+    base::FilePath exe_path(SysMultiByteTo16(python_path));
+
+    // Check for reasonable output, cmd may have output an error message.
+    if (base::PathExists(exe_path))
+      return exe_path;
+  }
+  return base::FilePath();
+}
+
+// python_exe_name and python_bat_name can be empty but cannot be absolute
+// paths. They should be "python.exe" or "", etc., and "python.bat" or "", etc.
+base::FilePath FindWindowsPython(const base::FilePath& python_exe_name,
+                                 const base::FilePath& python_bat_name) {
+  char16_t current_directory[MAX_PATH];
+  ::GetCurrentDirectory(MAX_PATH, reinterpret_cast<LPWSTR>(current_directory));
+
+  // First search for python.exe in the current directory.
+  if (!python_exe_name.empty()) {
+    CHECK(python_exe_name.FinalExtension() == u".exe");
+    CHECK_EQ(python_exe_name.IsAbsolute(), false);
+    base::FilePath cur_dir_candidate_exe =
+        base::FilePath(current_directory).Append(python_exe_name);
+    if (base::PathExists(cur_dir_candidate_exe))
+      return cur_dir_candidate_exe;
+  }
+
+  // Get the path.
+  const char16_t kPathEnvVarName[] = u"Path";
+  DWORD path_length = ::GetEnvironmentVariable(
+      reinterpret_cast<LPCWSTR>(kPathEnvVarName), nullptr, 0);
+  if (path_length == 0)
+    return base::FilePath();
+  std::unique_ptr<char16_t[]> full_path(new char16_t[path_length]);
+  DWORD actual_path_length = ::GetEnvironmentVariable(
+      reinterpret_cast<LPCWSTR>(kPathEnvVarName),
+      reinterpret_cast<LPWSTR>(full_path.get()), path_length);
+  CHECK_EQ(path_length, actual_path_length + 1);
+
+  // Search for python.exe in the path.
+  for (const auto& component : base::SplitStringPiece(
+           std::u16string_view(full_path.get(), path_length), u";",
+           base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
+    if (!python_exe_name.empty()) {
+      base::FilePath candidate_exe =
+          base::FilePath(component).Append(python_exe_name);
+      if (base::PathExists(candidate_exe))
+        return candidate_exe;
+    }
+
+    // Also allow python.bat, but convert into the .exe.
+    if (!python_bat_name.empty()) {
+      CHECK(python_bat_name.FinalExtension() == u".bat");
+      CHECK_EQ(python_bat_name.IsAbsolute(), false);
+      base::FilePath candidate_bat =
+          base::FilePath(component).Append(python_bat_name);
+      if (base::PathExists(candidate_bat)) {
+        base::FilePath python_exe = PythonBatToExe(candidate_bat);
+        if (!python_exe.empty())
+          return python_exe;
+      }
+    }
+  }
+  return base::FilePath();
+}
+#endif
+
+}  // namespace
+
+const char Setup::kBuildArgFileName[] = "args.gn";
+
+Setup::Setup()
+    : build_settings_(),
+      loader_(new LoaderImpl(&build_settings_)),
+      builder_(loader_.get()),
+      dotfile_settings_(&build_settings_, std::string()),
+      dotfile_scope_(&dotfile_settings_) {
+  dotfile_settings_.set_toolchain_label(Label());
+
+  build_settings_.set_item_defined_callback(
+      [task_runner = scheduler_.task_runner(),
+       builder = &builder_](std::unique_ptr<Item> item) {
+        ItemDefinedCallback(task_runner, builder, std::move(item));
+      });
+
+  loader_->set_complete_callback(&DecrementWorkCount);
+  // The scheduler's task runner wasn't created when the Loader was created, so
+  // we need to set it now.
+  loader_->set_task_runner(scheduler_.task_runner());
+}
+
+bool Setup::DoSetup(const std::string& build_dir, bool force_create) {
+  return DoSetup(build_dir, force_create,
+                 *base::CommandLine::ForCurrentProcess());
+}
+
+bool Setup::DoSetup(const std::string& build_dir,
+                    bool force_create,
+                    const base::CommandLine& cmdline) {
+  Err err;
+  if (!DoSetupWithErr(build_dir, force_create, cmdline, &err)) {
+    err.PrintToStdout();
+    return false;
+  }
+  DCHECK(!err.has_error());
+  return true;
+}
+
+bool Setup::DoSetupWithErr(const std::string& build_dir,
+                           bool force_create,
+                           const base::CommandLine& cmdline,
+                           Err* err) {
+  scheduler_.set_verbose_logging(cmdline.HasSwitch(switches::kVerbose));
+  if (cmdline.HasSwitch(switches::kTime) ||
+      cmdline.HasSwitch(switches::kTracelog))
+    EnableTracing();
+
+  ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "DoSetup");
+
+  if (!FillSourceDir(cmdline, err))
+    return false;
+  if (!RunConfigFile(err))
+    return false;
+  if (!FillOtherConfig(cmdline, err))
+    return false;
+
+  // Must be after FillSourceDir to resolve.
+  if (!FillBuildDir(build_dir, !force_create, err))
+    return false;
+
+  // Apply project-specific default (if specified).
+  // Must happen before FillArguments().
+  if (default_args_) {
+    Scope::KeyValueMap overrides;
+    default_args_->GetCurrentScopeValues(&overrides);
+    build_settings_.build_args().AddDefaultArgOverrides(overrides);
+  }
+
+  if (fill_arguments_) {
+    if (!FillArguments(cmdline, err))
+      return false;
+  }
+  if (!FillPythonPath(cmdline, err))
+    return false;
+
+  // Check for unused variables in the .gn file.
+  if (!dotfile_scope_.CheckForUnusedVars(err)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool Setup::Run() {
+  return Run(*base::CommandLine::ForCurrentProcess());
+}
+
+bool Setup::Run(const base::CommandLine& cmdline) {
+  RunPreMessageLoop();
+  if (!scheduler_.Run())
+    return false;
+  return RunPostMessageLoop(cmdline);
+}
+
+SourceFile Setup::GetBuildArgFile() const {
+  return SourceFile(build_settings_.build_dir().value() + kBuildArgFileName);
+}
+
+void Setup::RunPreMessageLoop() {
+  // Will be decremented with the loader is drained.
+  g_scheduler->IncrementWorkCount();
+
+  // Load the root build file.
+  loader_->Load(root_build_file_, LocationRange(), Label());
+}
+
+bool Setup::RunPostMessageLoop(const base::CommandLine& cmdline) {
+  Err err;
+  if (!builder_.CheckForBadItems(&err)) {
+    err.PrintToStdout();
+    return false;
+  }
+
+  if (!build_settings_.build_args().VerifyAllOverridesUsed(&err)) {
+    if (cmdline.HasSwitch(switches::kFailOnUnusedArgs)) {
+      err.PrintToStdout();
+      return false;
+    }
+    err.PrintNonfatalToStdout();
+    OutputString(
+        "\nThe build continued as if that argument was "
+        "unspecified.\n\n");
+    // Nonfatal error.
+  }
+
+  if (check_public_headers_) {
+    std::vector<const Target*> all_targets = builder_.GetAllResolvedTargets();
+    std::vector<const Target*> to_check;
+    if (check_patterns()) {
+      commands::FilterTargetsByPatterns(all_targets, *check_patterns(),
+                                        &to_check);
+    } else if (no_check_patterns()) {
+      commands::FilterOutTargetsByPatterns(all_targets, *no_check_patterns(),
+                                           &to_check);
+    } else {
+      to_check = all_targets;
+    }
+
+    if (!commands::CheckPublicHeaders(&build_settings_, all_targets, to_check,
+                                      false, false, check_system_includes_)) {
+      return false;
+    }
+  }
+
+  // Write out tracing and timing if requested.
+  if (cmdline.HasSwitch(switches::kTime))
+    PrintLongHelp(SummarizeTraces());
+  if (cmdline.HasSwitch(switches::kTracelog))
+    SaveTraces(cmdline.GetSwitchValuePath(switches::kTracelog));
+
+  return true;
+}
+
+bool Setup::FillArguments(const base::CommandLine& cmdline, Err* err) {
+  // Use the args on the command line if specified, and save them. Do this even
+  // if the list is empty (this means clear any defaults).
+  // If --args is not set, args.gn file does not exist and gen_empty_args
+  // is set, generate an empty args.gn file with default comments.
+
+  base::FilePath build_arg_file =
+      build_settings_.GetFullPath(GetBuildArgFile());
+  auto switch_value = cmdline.GetSwitchValueASCII(switches::kArgs);
+  if (cmdline.HasSwitch(switches::kArgs) ||
+      (gen_empty_args_ && !PathExists(build_arg_file))) {
+    if (!FillArgsFromCommandLine(
+            switch_value.empty() ? kDefaultArgsGn : switch_value, err)) {
+      return false;
+    }
+    SaveArgsToFile();
+    return true;
+  }
+
+  // No command line args given, use the arguments from the build dir (if any).
+  return FillArgsFromFile(err);
+}
+
+bool Setup::FillArgsFromCommandLine(const std::string& args, Err* err) {
+  args_input_file_ = std::make_unique<InputFile>(SourceFile());
+  args_input_file_->SetContents(args);
+  args_input_file_->set_friendly_name("the command-line \"--args\"");
+  return FillArgsFromArgsInputFile(err);
+}
+
+bool Setup::FillArgsFromFile(Err* err) {
+  ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Load args file");
+
+  SourceFile build_arg_source_file = GetBuildArgFile();
+  base::FilePath build_arg_file =
+      build_settings_.GetFullPath(build_arg_source_file);
+
+  std::string contents;
+  if (!base::ReadFileToString(build_arg_file, &contents))
+    return true;  // File doesn't exist, continue with default args.
+
+  // Add a dependency on the build arguments file. If this changes, we want
+  // to re-generate the build.
+  g_scheduler->AddGenDependency(build_arg_file);
+
+  if (contents.empty())
+    return true;  // Empty file, do nothing.
+
+  args_input_file_ = std::make_unique<InputFile>(build_arg_source_file);
+  args_input_file_->SetContents(contents);
+  args_input_file_->set_friendly_name(
+      "build arg file (use \"gn args <out_dir>\" to edit)");
+
+  setup_trace.Done();  // Only want to count the load as part of the trace.
+  return FillArgsFromArgsInputFile(err);
+}
+
+bool Setup::FillArgsFromArgsInputFile(Err* err) {
+  ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Parse args");
+
+  args_tokens_ = Tokenizer::Tokenize(args_input_file_.get(), err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  args_root_ = Parser::Parse(args_tokens_, err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  Scope arg_scope(&dotfile_settings_);
+  // Set soure dir so relative imports in args work.
+  SourceDir root_source_dir =
+      SourceDirForCurrentDirectory(build_settings_.root_path());
+  arg_scope.set_source_dir(root_source_dir);
+  args_root_->Execute(&arg_scope, err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  // Save the result of the command args.
+  Scope::KeyValueMap overrides;
+  arg_scope.GetCurrentScopeValues(&overrides);
+  build_settings_.build_args().AddArgOverrides(overrides);
+  build_settings_.build_args().set_build_args_dependency_files(
+      arg_scope.build_dependency_files());
+  return true;
+}
+
+bool Setup::SaveArgsToFile() {
+  ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Save args file");
+
+  // For the first run, the build output dir might not be created yet, so do
+  // that so we can write a file into it. Ignore errors, we'll catch the error
+  // when we try to write a file to it below.
+  base::FilePath build_arg_file =
+      build_settings_.GetFullPath(GetBuildArgFile());
+  base::CreateDirectory(build_arg_file.DirName());
+
+  std::string contents = args_input_file_->contents();
+  commands::FormatStringToString(contents, commands::TreeDumpMode::kInactive,
+                                 &contents, nullptr);
+#if defined(OS_WIN)
+  // Use Windows lineendings for this file since it will often open in
+  // Notepad which can't handle Unix ones.
+  base::ReplaceSubstringsAfterOffset(&contents, 0, "\n", "\r\n");
+#endif
+  if (base::WriteFile(build_arg_file, contents.c_str(),
+                      static_cast<int>(contents.size())) == -1) {
+    Err(Location(), "Args file could not be written.",
+        "The file is \"" + FilePathToUTF8(build_arg_file) + "\"")
+        .PrintToStdout();
+    return false;
+  }
+
+  // Add a dependency on the build arguments file. If this changes, we want
+  // to re-generate the build.
+  g_scheduler->AddGenDependency(build_arg_file);
+
+  return true;
+}
+
+bool Setup::FillSourceDir(const base::CommandLine& cmdline, Err* err) {
+  // Find the .gn file.
+  base::FilePath root_path;
+
+  // Prefer the command line args to the config file.
+  base::FilePath relative_root_path =
+      cmdline.GetSwitchValuePath(switches::kRoot);
+  if (!relative_root_path.empty()) {
+    root_path = base::MakeAbsoluteFilePath(relative_root_path);
+    if (root_path.empty()) {
+      *err = Err(Location(), "Root source path not found.",
+                 "The path \"" + FilePathToUTF8(relative_root_path) +
+                     "\" doesn't exist.");
+      return false;
+    }
+
+    // When --root is specified, an alternate --dotfile can also be set.
+    // --dotfile should be a real file path and not a "//foo" source-relative
+    // path.
+    base::FilePath dotfile_path =
+        cmdline.GetSwitchValuePath(switches::kDotfile);
+    if (dotfile_path.empty()) {
+      dotfile_name_ = root_path.Append(kGnFile);
+    } else {
+      dotfile_name_ = base::MakeAbsoluteFilePath(dotfile_path);
+      if (dotfile_name_.empty()) {
+        *err = Err(Location(), "Could not load dotfile.",
+                   "The file \"" + FilePathToUTF8(dotfile_path) +
+                       "\" couldn't be loaded.");
+        return false;
+      }
+      // Only set dotfile_name if it was passed explicitly.
+      build_settings_.set_dotfile_name(dotfile_name_);
+    }
+  } else {
+    // In the default case, look for a dotfile and that also tells us where the
+    // source root is.
+    base::FilePath cur_dir;
+    base::GetCurrentDirectory(&cur_dir);
+    dotfile_name_ = FindDotFile(cur_dir);
+    if (dotfile_name_.empty()) {
+      *err = Err(
+          Location(), "Can't find source root.",
+          "I could not find a \".gn\" file in the current directory or any "
+          "parent,\nand the --root command-line argument was not specified.");
+      return false;
+    }
+    root_path = dotfile_name_.DirName();
+  }
+
+  base::FilePath root_realpath = base::MakeAbsoluteFilePath(root_path);
+  if (root_realpath.empty()) {
+    *err = Err(Location(), "Can't get the real root path.",
+               "I could not get the real path of \"" +
+                   FilePathToUTF8(root_path) + "\".");
+    return false;
+  }
+  if (scheduler_.verbose_logging())
+    scheduler_.Log("Using source root", FilePathToUTF8(root_realpath));
+  build_settings_.SetRootPath(root_realpath);
+
+  return true;
+}
+
+bool Setup::FillBuildDir(const std::string& build_dir,
+                         bool require_exists,
+                         Err* err) {
+  SourceDir resolved =
+      SourceDirForCurrentDirectory(build_settings_.root_path())
+          .ResolveRelativeDir(Value(nullptr, build_dir), err,
+                              build_settings_.root_path_utf8());
+  if (err->has_error()) {
+    return false;
+  }
+
+  base::FilePath build_dir_path = build_settings_.GetFullPath(resolved);
+  if (!base::CreateDirectory(build_dir_path)) {
+    *err = Err(Location(), "Can't create the build dir.",
+               "I could not create the build dir \"" +
+                   FilePathToUTF8(build_dir_path) + "\".");
+    return false;
+  }
+  base::FilePath build_dir_realpath =
+      base::MakeAbsoluteFilePath(build_dir_path);
+  if (build_dir_realpath.empty()) {
+    *err = Err(Location(), "Can't get the real build dir path.",
+               "I could not get the real path of \"" +
+                   FilePathToUTF8(build_dir_path) + "\".");
+    return false;
+  }
+  resolved = SourceDirForPath(build_settings_.root_path(), build_dir_realpath);
+
+  if (scheduler_.verbose_logging())
+    scheduler_.Log("Using build dir", resolved.value());
+
+  if (require_exists) {
+    if (!base::PathExists(
+            build_dir_path.Append(FILE_PATH_LITERAL("build.ninja")))) {
+      *err = Err(
+          Location(), "Not a build directory.",
+          "This command requires an existing build directory. I interpreted "
+          "your input\n\"" +
+              build_dir + "\" as:\n  " + FilePathToUTF8(build_dir_path) +
+              "\nwhich doesn't seem to contain a previously-generated build.");
+      return false;
+    }
+  }
+
+  build_settings_.SetBuildDir(resolved);
+  return true;
+}
+
+// On Chromium repositories on Windows the Python executable can be specified as
+// python, python.bat, or python.exe (ditto for python3, and with or without a
+// full path specification). This handles all of these cases and returns a fully
+// specified path to a .exe file.
+// This is currently a NOP on other platforms.
+base::FilePath ProcessFileExtensions(base::FilePath script_executable) {
+#if defined(OS_WIN)
+  // If we have a relative path with no extension such as "python" or
+  // "python3" then do a path search on the name with .exe and .bat appended.
+  auto extension = script_executable.FinalExtension();
+  if (script_executable.IsAbsolute()) {
+    // Do translation from .bat to .exe but otherwise just pass through.
+    if (extension == u".bat")
+      script_executable = PythonBatToExe(script_executable);
+  } else {
+    if (extension == u"") {
+      // If no extension is specified then search the path for .exe and .bat
+      // variants.
+      script_executable =
+          FindWindowsPython(script_executable.ReplaceExtension(u".exe"),
+                            script_executable.ReplaceExtension(u".bat"));
+    } else if (extension == u".bat") {
+      // Search the path just for the specified .bat.
+      script_executable =
+          FindWindowsPython(base::FilePath(), script_executable);
+    } else if (extension == u".exe") {
+      // Search the path just for the specified .exe.
+      script_executable =
+          FindWindowsPython(script_executable, base::FilePath());
+    }
+  }
+  script_executable = script_executable.NormalizePathSeparatorsTo('/');
+#endif
+  return script_executable;
+}
+
+bool Setup::FillPythonPath(const base::CommandLine& cmdline, Err* err) {
+  // Trace this since it tends to be a bit slow on Windows.
+  ScopedTrace setup_trace(TraceItem::TRACE_SETUP, "Fill Python Path");
+  const Value* value = dotfile_scope_.GetValue("script_executable", true);
+  if (cmdline.HasSwitch(switches::kScriptExecutable)) {
+    auto script_executable =
+        cmdline.GetSwitchValuePath(switches::kScriptExecutable);
+    build_settings_.set_python_path(ProcessFileExtensions(script_executable));
+  } else if (value) {
+    if (!value->VerifyTypeIs(Value::STRING, err)) {
+      return false;
+    }
+    build_settings_.set_python_path(
+        ProcessFileExtensions(UTF8ToFilePath(value->string_value())));
+  } else {
+#if defined(OS_WIN)
+    base::FilePath python_path =
+        ProcessFileExtensions(base::FilePath(u"python"));
+    if (!python_path.IsAbsolute()) {
+      scheduler_.Log("WARNING",
+                     "Could not find python on path, using "
+                     "just \"python.exe\"");
+      python_path = base::FilePath(u"python.exe");
+    }
+    build_settings_.set_python_path(python_path);
+#else
+    build_settings_.set_python_path(base::FilePath("python"));
+#endif
+  }
+  return true;
+}
+
+bool Setup::RunConfigFile(Err* err) {
+  if (scheduler_.verbose_logging())
+    scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_));
+
+  dotfile_input_file_ = std::make_unique<InputFile>(SourceFile("//.gn"));
+  if (!dotfile_input_file_->Load(dotfile_name_)) {
+    *err = Err(Location(), "Could not load dotfile.",
+               "The file \"" + FilePathToUTF8(dotfile_name_) +
+                   "\" couldn't be loaded");
+    return false;
+  }
+
+  dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  dotfile_root_ = Parser::Parse(dotfile_tokens_, err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  // Add a dependency on the build arguments file. If this changes, we want
+  // to re-generate the build. This causes the dotfile to make it into
+  // build.ninja.d.
+  g_scheduler->AddGenDependency(dotfile_name_);
+
+  // Also add a build dependency to the scope, which is used by `gn analyze`.
+  dotfile_scope_.AddBuildDependencyFile(SourceFile("//.gn"));
+  dotfile_root_->Execute(&dotfile_scope_, err);
+  if (err->has_error()) {
+    return false;
+  }
+
+  return true;
+}
+
+bool Setup::FillOtherConfig(const base::CommandLine& cmdline, Err* err) {
+  SourceDir current_dir("//");
+  Label root_target_label(current_dir, "");
+
+  // Secondary source path, read from the config file if present.
+  // Read from the config file if present.
+  const Value* secondary_value =
+      dotfile_scope_.GetValue("secondary_source", true);
+  if (secondary_value) {
+    if (!secondary_value->VerifyTypeIs(Value::STRING, err)) {
+      return false;
+    }
+    build_settings_.SetSecondarySourcePath(
+        SourceDir(secondary_value->string_value()));
+  }
+
+  // Build file names.
+  const Value* build_file_extension_value =
+      dotfile_scope_.GetValue("build_file_extension", true);
+  if (build_file_extension_value) {
+    if (!build_file_extension_value->VerifyTypeIs(Value::STRING, err)) {
+      return false;
+    }
+
+    std::string extension = build_file_extension_value->string_value();
+    auto normalized_extension = UTF8ToFilePath(extension).value();
+    if (normalized_extension.find_first_of(base::FilePath::kSeparators) !=
+        base::FilePath::StringType::npos) {
+      *err = Err(Location(), "Build file extension '" + extension +
+                                 "' cannot " + "contain a path separator");
+      return false;
+    }
+    loader_->set_build_file_extension(extension);
+  }
+
+  // Ninja required version.
+  const Value* ninja_required_version_value =
+      dotfile_scope_.GetValue("ninja_required_version", true);
+  if (ninja_required_version_value) {
+    if (!ninja_required_version_value->VerifyTypeIs(Value::STRING, err)) {
+      return false;
+    }
+    std::optional<Version> version =
+        Version::FromString(ninja_required_version_value->string_value());
+    if (!version) {
+      Err(Location(), "Invalid Ninja version '" +
+                          ninja_required_version_value->string_value() + "'")
+          .PrintToStdout();
+      return false;
+    }
+    build_settings_.set_ninja_required_version(*version);
+  }
+
+  // Root build file.
+  if (cmdline.HasSwitch(switches::kRootTarget)) {
+    auto switch_value = cmdline.GetSwitchValueASCII(switches::kRootTarget);
+    Value root_value(nullptr, switch_value);
+    root_target_label = Label::Resolve(current_dir, std::string_view(), Label(),
+                                       root_value, err);
+    if (err->has_error()) {
+      return false;
+    }
+    if (dotfile_scope_.GetValue("root", true)) {
+      // The "kRootTarget" switch overwrites the "root" variable in ".gn".
+      dotfile_scope_.MarkUsed("root");
+    }
+  } else {
+    const Value* root_value = dotfile_scope_.GetValue("root", true);
+    if (root_value) {
+      if (!root_value->VerifyTypeIs(Value::STRING, err)) {
+        return false;
+      }
+
+      root_target_label = Label::Resolve(current_dir, std::string_view(),
+                                         Label(), *root_value, err);
+      if (err->has_error()) {
+        return false;
+      }
+    }
+  }
+  // Set the root build file here in order to take into account the values of
+  // "build_file_extension" and "root".
+  root_build_file_ = loader_->BuildFileForLabel(root_target_label);
+  build_settings_.SetRootTargetLabel(root_target_label);
+
+  // Build config file.
+  const Value* build_config_value =
+      dotfile_scope_.GetValue("buildconfig", true);
+  if (!build_config_value) {
+    Err(Location(), "No build config file.",
+        "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
+            "\")\n"
+            "didn't specify a \"buildconfig\" value.")
+        .PrintToStdout();
+    return false;
+  } else if (!build_config_value->VerifyTypeIs(Value::STRING, err)) {
+    return false;
+  }
+  build_settings_.set_build_config_file(
+      SourceFile(build_config_value->string_value()));
+
+  // Targets to check.
+  const Value* check_targets_value =
+      dotfile_scope_.GetValue("check_targets", true);
+  if (check_targets_value) {
+    check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
+    ExtractListOfLabelPatterns(&build_settings_, *check_targets_value,
+                               current_dir, check_patterns_.get(), err);
+    if (err->has_error()) {
+      return false;
+    }
+  }
+
+  // Targets not to check.
+  const Value* no_check_targets_value =
+      dotfile_scope_.GetValue("no_check_targets", true);
+  if (no_check_targets_value) {
+    if (check_targets_value) {
+      Err(Location(), "Conflicting check settings.",
+          "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) +
+              "\")\n"
+              "specified both check_targets and no_check_targets and at most "
+              "one is allowed.")
+          .PrintToStdout();
+      return false;
+    }
+    no_check_patterns_ = std::make_unique<std::vector<LabelPattern>>();
+    ExtractListOfLabelPatterns(&build_settings_, *no_check_targets_value,
+                               current_dir, no_check_patterns_.get(), err);
+    if (err->has_error()) {
+      return false;
+    }
+  }
+
+  const Value* check_system_includes_value =
+      dotfile_scope_.GetValue("check_system_includes", true);
+  if (check_system_includes_value) {
+    if (!check_system_includes_value->VerifyTypeIs(Value::BOOLEAN, err)) {
+      return false;
+    }
+    check_system_includes_ = check_system_includes_value->boolean_value();
+  }
+
+  // Fill exec_script_whitelist.
+  const Value* exec_script_whitelist_value =
+      dotfile_scope_.GetValue("exec_script_whitelist", true);
+  if (exec_script_whitelist_value) {
+    // Fill the list of targets to check.
+    if (!exec_script_whitelist_value->VerifyTypeIs(Value::LIST, err)) {
+      return false;
+    }
+    std::unique_ptr<SourceFileSet> whitelist =
+        std::make_unique<SourceFileSet>();
+    for (const auto& item : exec_script_whitelist_value->list_value()) {
+      if (!item.VerifyTypeIs(Value::STRING, err)) {
+        return false;
+      }
+      whitelist->insert(current_dir.ResolveRelativeFile(item, err));
+      if (err->has_error()) {
+        return false;
+      }
+    }
+    build_settings_.set_exec_script_whitelist(std::move(whitelist));
+  }
+
+  // Fill optional default_args.
+  const Value* default_args_value =
+      dotfile_scope_.GetValue("default_args", true);
+  if (default_args_value) {
+    if (!default_args_value->VerifyTypeIs(Value::SCOPE, err)) {
+      return false;
+    }
+
+    default_args_ = default_args_value->scope_value();
+  }
+
+  const Value* arg_file_template_value =
+      dotfile_scope_.GetValue("arg_file_template", true);
+  if (arg_file_template_value) {
+    if (!arg_file_template_value->VerifyTypeIs(Value::STRING, err)) {
+      return false;
+    }
+    SourceFile path(arg_file_template_value->string_value());
+    build_settings_.set_arg_file_template_path(path);
+  }
+
+  return true;
+}
diff --git a/src/gn/setup.h b/src/gn/setup.h
new file mode 100644 (file)
index 0000000..1c5d280
--- /dev/null
@@ -0,0 +1,214 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SETUP_H_
+#define TOOLS_GN_SETUP_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/label_pattern.h"
+#include "gn/loader.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/token.h"
+#include "gn/toolchain.h"
+
+class InputFile;
+class ParseNode;
+
+namespace base {
+class CommandLine;
+}
+
+extern const char kDotfile_Help[];
+
+// Helper class to set up the build settings and environment for the various
+// commands to run.
+class Setup {
+ public:
+  Setup();
+
+  // Configures the build for the current command line. On success returns
+  // true. On failure, prints the error and returns false.
+  //
+  // The parameter is the string the user specified for the build directory. We
+  // will try to interpret this as a SourceDir if possible, and will fail if is
+  // is malformed.
+  //
+  // With force_create = false, setup will fail if the build directory doesn't
+  // already exist with an args file in it. With force_create set to true, the
+  // directory will be created if necessary. Commands explicitly doing
+  // generation should set this to true to create it, but querying commands
+  // should set it to false to prevent creating oddly-named directories in case
+  // the user omits the build directory argument (which is easy to do).
+  //
+  // cmdline is the gn invocation command, with flags like --root and --dotfile.
+  // If no explicit cmdline is passed, base::CommandLine::ForCurrentProcess()
+  // is used.
+  bool DoSetup(const std::string& build_dir, bool force_create);
+  bool DoSetup(const std::string& build_dir,
+               bool force_create,
+               const base::CommandLine& cmdline);
+
+  // Same as DoSetup() but used for tests to capture error output.
+  bool DoSetupWithErr(const std::string& build_dir,
+               bool force_create,
+               const base::CommandLine& cmdline,
+               Err* err);
+
+  // Runs the load, returning true on success. On failure, prints the error
+  // and returns false. This includes both RunPreMessageLoop() and
+  // RunPostMessageLoop().
+  //
+  // cmdline is the gn invocation command, with flags like --root and --dotfile.
+  // If no explicit cmdline is passed, base::CommandLine::ForCurrentProcess()
+  // is used.
+  bool Run();
+  bool Run(const base::CommandLine& cmdline);
+
+  Scheduler& scheduler() { return scheduler_; }
+
+  // Returns the file used to store the build arguments. Note that the path
+  // might not exist.
+  SourceFile GetBuildArgFile() const;
+
+  // Sets whether the build arguments should be filled during setup from the
+  // command line/build argument file. This will be true by default. The use
+  // case for setting it to false is when editing build arguments, we don't
+  // want to rely on them being valid.
+  void set_fill_arguments(bool fa) { fill_arguments_ = fa; }
+
+  // After a successful run, setting this will additionally cause the public
+  // headers to be checked. Defaults to false.
+  void set_check_public_headers(bool s) { check_public_headers_ = s; }
+
+  // After a successful run, setting this will additionally cause system style
+  // includes to be checked. Defaults to false.
+  void set_check_system_includes(bool s) { check_system_includes_ = s; }
+
+  bool check_system_includes() const { return check_system_includes_; }
+
+  // Before DoSetup, setting this will generate an empty args.gn if
+  // it does not exist and set up correct dependencies for it.
+  void set_gen_empty_args(bool ge) { gen_empty_args_ = ge; }
+
+  // Read from the .gn file, these are the targets to check. If the .gn file
+  // does not specify anything, this will be null. If the .gn file specifies
+  // the empty list, this will be non-null but empty.
+  const std::vector<LabelPattern>* check_patterns() const {
+    return check_patterns_.get();
+  }
+
+  // Read from the .gn file, these are the targets *not* to check. If the .gn
+  // file does not specify anything, this will be null. If the .gn file
+  // specifies the empty list, this will be non-null but empty. At least one of
+  // check_patterns() and no_check_patterns() will be null.
+  const std::vector<LabelPattern>* no_check_patterns() const {
+    return no_check_patterns_.get();
+  }
+
+  BuildSettings& build_settings() { return build_settings_; }
+  Builder& builder() { return builder_; }
+  LoaderImpl* loader() { return loader_.get(); }
+
+  const SourceFile& GetDotFile() const { return dotfile_input_file_->name(); }
+
+  // Name of the file in the root build directory that contains the build
+  // arguments.
+  static const char kBuildArgFileName[];
+
+ private:
+  // Performs the two sets of operations to run the generation before and after
+  // the message loop is run.
+  void RunPreMessageLoop();
+  bool RunPostMessageLoop(const base::CommandLine& cmdline);
+
+  // Fills build arguments. Returns true on success.
+  bool FillArguments(const base::CommandLine& cmdline, Err* err);
+
+  // Fills the build arguments from the command line or from the build arg file.
+  bool FillArgsFromCommandLine(const std::string& args, Err* err);
+  bool FillArgsFromFile(Err* err);
+
+  // Given an already-loaded args_input_file_, parses and saves the resulting
+  // arguments. Backend for the different FillArgs variants.
+  bool FillArgsFromArgsInputFile(Err* err);
+
+  // Writes the build arguments to the build arg file.
+  bool SaveArgsToFile();
+
+  // Fills the root directory into the settings. Returns true on success, or
+  // |err| filled out.
+  bool FillSourceDir(const base::CommandLine& cmdline, Err* err);
+
+  // Fills the build directory given the value the user has specified.
+  // Must happen after FillSourceDir so we can resolve source-relative
+  // paths. If require_exists is false, it will fail if the dir doesn't exist.
+  bool FillBuildDir(const std::string& build_dir,
+                    bool require_exists,
+                    Err* err);
+
+  // Fills the python path portion of the command line. On failure, sets
+  // it to just "python".
+  bool FillPythonPath(const base::CommandLine& cmdline, Err* err);
+
+  // Run config file.
+  bool RunConfigFile(Err* err);
+
+  bool FillOtherConfig(const base::CommandLine& cmdline, Err* err);
+
+  BuildSettings build_settings_;
+  scoped_refptr<LoaderImpl> loader_;
+  Builder builder_;
+
+  SourceFile root_build_file_;
+
+  bool check_public_headers_ = false;
+  bool check_system_includes_ = false;
+
+  // See getter for info.
+  std::unique_ptr<std::vector<LabelPattern>> check_patterns_;
+  std::unique_ptr<std::vector<LabelPattern>> no_check_patterns_;
+
+  Scheduler scheduler_;
+
+  // These settings and toolchain are used to interpret the command line and
+  // dot file.
+  Settings dotfile_settings_;
+  Scope dotfile_scope_;
+
+  // State for invoking the dotfile.
+  base::FilePath dotfile_name_;
+  std::unique_ptr<InputFile> dotfile_input_file_;
+  std::vector<Token> dotfile_tokens_;
+  std::unique_ptr<ParseNode> dotfile_root_;
+
+  // Default overrides, specified in the dotfile.
+  // Owned by the Value (if it exists) in the dotfile_scope_.
+  const Scope* default_args_ = nullptr;
+
+  // Set to true when we should populate the build arguments from the command
+  // line or build argument file. See setter above.
+  bool fill_arguments_ = true;
+
+  // Generate an empty args.gn file if it does not exists.
+  bool gen_empty_args_ = false;
+
+  // State for invoking the command line args. We specifically want to keep
+  // this around for the entire run so that Values can blame to the command
+  // line when we issue errors about them.
+  std::unique_ptr<InputFile> args_input_file_;
+  std::vector<Token> args_tokens_;
+  std::unique_ptr<ParseNode> args_root_;
+
+  DISALLOW_COPY_AND_ASSIGN(Setup);
+};
+
+#endif  // TOOLS_GN_SETUP_H_
diff --git a/src/gn/setup_unittest.cc b/src/gn/setup_unittest.cc
new file mode 100644 (file)
index 0000000..af10a6e
--- /dev/null
@@ -0,0 +1,94 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/setup.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "gn/filesystem_utils.h"
+#include "gn/switches.h"
+#include "gn/test_with_scheduler.h"
+#include "util/build_config.h"
+
+using SetupTest = TestWithScheduler;
+
+static void WriteFile(const base::FilePath& file, const std::string& data) {
+  CHECK_EQ(static_cast<int>(data.size()),  // Way smaller than INT_MAX.
+           base::WriteFile(file, data.data(), data.size()));
+}
+
+TEST_F(SetupTest, DotGNFileIsGenDep) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Create a temp directory containing a .gn file and a BUILDCONFIG.gn file,
+  // pass it as --root.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+  base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
+  WriteFile(dot_gn_name, "buildconfig = \"//BUILDCONFIG.gn\"\n");
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that the .gn file is in the scheduler's gen deps.
+  Setup setup;
+  EXPECT_TRUE(
+      setup.DoSetup(FilePathToUTF8(build_temp_dir.GetPath()), true, cmdline));
+  std::vector<base::FilePath> gen_deps = g_scheduler->GetGenDependencies();
+  ASSERT_EQ(1u, gen_deps.size());
+  EXPECT_EQ(gen_deps[0], base::MakeAbsoluteFilePath(dot_gn_name));
+}
+
+static void RunExtensionCheckTest(std::string extension,
+                                  bool success,
+                                  const std::string& expected_error_message) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Create a temp directory containing a .gn file and a BUILDCONFIG.gn file,
+  // pass it as --root.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+  base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
+  WriteFile(dot_gn_name,
+            "buildconfig = \"//BUILDCONFIG.gn\"\n\
+      build_file_extension = \"" +
+                extension + "\"");
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that its status.
+  Setup setup;
+  Err err;
+  EXPECT_EQ(success,
+            setup.DoSetupWithErr(FilePathToUTF8(build_temp_dir.GetPath()), true,
+                                 cmdline, &err));
+  EXPECT_EQ(success, !err.has_error());
+  EXPECT_EQ(expected_error_message, err.message());
+}
+
+TEST_F(SetupTest, NoSeparatorInExtension) {
+  RunExtensionCheckTest(
+      "hello" + std::string(1, base::FilePath::kSeparators[0]) + "world", false,
+#if defined(OS_WIN)
+      "Build file extension 'hello\\world' cannot contain a path separator"
+#else
+      "Build file extension 'hello/world' cannot contain a path separator"
+#endif
+        );
+}
+
+TEST_F(SetupTest, Extension) {
+  RunExtensionCheckTest("yay", true, "");
+}
diff --git a/src/gn/source_dir.cc b/src/gn/source_dir.cc
new file mode 100644 (file)
index 0000000..ce92cf2
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/source_dir.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "gn/filesystem_utils.h"
+#include "gn/source_file.h"
+#include "util/build_config.h"
+
+namespace {
+
+void AssertValueSourceDirString(const std::string_view s) {
+  if (!s.empty()) {
+#if defined(OS_WIN)
+    DCHECK(s[0] == '/' ||
+           (s.size() > 2 && s[0] != '/' && s[1] == ':' && IsSlash(s[2])));
+#else
+    DCHECK(s[0] == '/');
+#endif
+    DCHECK(EndsWithSlash(s)) << s;
+  }
+}
+
+// Validates input value (input_value) and sets proper error message.
+// Note: Parameter blame_input is used only for generating error message.
+template <typename StringType>
+bool ValidateResolveInput(bool as_file,
+                          const Value& blame_input_value,
+                          const StringType& input_value,
+                          Err* err) {
+  if (as_file) {
+    // It's an error to resolve an empty string or one that is a directory
+    // (indicated by a trailing slash) because this is the function that expects
+    // to return a file.
+    if (input_value.empty()) {
+      *err = Err(blame_input_value, "Empty file path.",
+                 "You can't use empty strings as file paths.");
+      return false;
+    } else if (input_value[input_value.size() - 1] == '/') {
+      std::string help = "You specified the path\n  ";
+      help.append(std::string(input_value));
+      help.append(
+          "\nand it ends in a slash, indicating you think it's a directory."
+          "\nBut here you're supposed to be listing a file.");
+      *err = Err(blame_input_value, "File path ends in a slash.", help);
+      return false;
+    }
+  } else if (input_value.empty()) {
+    *err = Err(blame_input_value, "Empty directory path.",
+               "You can't use empty strings as directories.");
+    return false;
+  }
+  return true;
+}
+
+static StringAtom SourceDirStringAtom(const std::string_view s) {
+  if (EndsWithSlash(s)) {  // Avoid allocation when possible.
+    AssertValueSourceDirString(s);
+    return StringAtom(s);
+  }
+
+  std::string str;
+  str.reserve(s.size() + 1);
+  str += s;
+  str.push_back('/');
+  AssertValueSourceDirString(str);
+  return StringAtom(str);
+}
+
+}  // namespace
+
+SourceDir::SourceDir(const std::string_view s)
+    : value_(SourceDirStringAtom(s)) {}
+
+template <typename StringType>
+std::string SourceDir::ResolveRelativeAs(
+    bool as_file,
+    const Value& blame_input_value,
+    const StringType& input_value,
+    Err* err,
+    const std::string_view& source_root) const {
+  if (!ValidateResolveInput<StringType>(as_file, blame_input_value, input_value,
+                                        err)) {
+    return std::string();
+  }
+  return ResolveRelative(input_value, value_.str(), as_file, source_root);
+}
+
+SourceFile SourceDir::ResolveRelativeFile(
+    const Value& p,
+    Err* err,
+    const std::string_view& source_root) const {
+  SourceFile ret;
+
+  if (!p.VerifyTypeIs(Value::STRING, err))
+    return ret;
+
+  const std::string& input_string = p.string_value();
+  if (!ValidateResolveInput<std::string>(true, p, input_string, err))
+    return ret;
+
+  ret.SetValue(ResolveRelative(input_string, value_.str(), true, source_root));
+  return ret;
+}
+
+std::string SourceDir::ResolveRelativeAs(bool as_file,
+                                         const Value& v,
+                                         Err* err,
+                                         const std::string_view& source_root,
+                                         const std::string* v_value) const {
+  if (!v.VerifyTypeIs(Value::STRING, err))
+    return std::string();
+
+  if (!v_value) {
+    v_value = &v.string_value();
+  }
+  std::string result =
+      ResolveRelativeAs(as_file, v, *v_value, err, source_root);
+  if (!as_file)
+    AssertValueSourceDirString(result);
+  return result;
+}
+
+SourceDir SourceDir::ResolveRelativeDir(
+    const Value& v,
+    Err* err,
+    const std::string_view& source_root) const {
+  if (!v.VerifyTypeIs(Value::STRING, err))
+    return SourceDir();
+
+  return ResolveRelativeDir<std::string>(v, v.string_value(), err, source_root);
+}
+
+base::FilePath SourceDir::Resolve(const base::FilePath& source_root) const {
+  return ResolvePath(value_.str(), false, source_root);
+}
+
+// Explicit template instantiation
+template std::string SourceDir::ResolveRelativeAs(
+    bool as_file,
+    const Value& blame_input_value,
+    const std::string& input_value,
+    Err* err,
+    const std::string_view& source_root) const;
+
+template std::string SourceDir::ResolveRelativeAs(
+    bool as_file,
+    const Value& blame_input_value,
+    const std::string_view& input_value,
+    Err* err,
+    const std::string_view& source_root) const;
diff --git a/src/gn/source_dir.h b/src/gn/source_dir.h
new file mode 100644 (file)
index 0000000..5c436f4
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SOURCE_DIR_H_
+#define TOOLS_GN_SOURCE_DIR_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+
+#include "gn/string_atom.h"
+
+class Err;
+class SourceFile;
+class Value;
+
+// Represents a directory within the source tree. Source dirs begin and end in
+// slashes.
+//
+// If there is one slash at the beginning, it will mean a system-absolute file
+// path. On Windows, absolute system paths will be of the form "/C:/foo/bar".
+//
+// Two slashes at the beginning indicate a path relative to the source root.
+class SourceDir {
+ public:
+  SourceDir() = default;
+
+  SourceDir(const std::string_view s);
+
+  // Resolves a file or dir name (based on as_file parameter) relative
+  // to this source directory. Will return an empty string on error
+  // and set the give *err pointer (required). Empty input is always an error.
+  //
+  // Passed non null v_value will be used to resolve path (in cases where
+  // a substring has been extracted from the value, as with label resolution).
+  // In this use case parameter v is used to generate proper error.
+  //
+  // If source_root is supplied, these functions will additionally handle the
+  // case where the input is a system-absolute but still inside the source
+  // tree. This is the case for some external tools.
+  std::string ResolveRelativeAs(
+      bool as_file,
+      const Value& v,
+      Err* err,
+      const std::string_view& source_root = std::string_view(),
+      const std::string* v_value = nullptr) const;
+
+  // Like ResolveRelativeAs above, but allows one to produce result
+  // without overhead for string conversion (on input value).
+  template <typename StringType>
+  std::string ResolveRelativeAs(
+      bool as_file,
+      const Value& blame_input_value,
+      const StringType& input_value,
+      Err* err,
+      const std::string_view& source_root = std::string_view()) const;
+
+  // Wrapper for ResolveRelativeAs.
+  SourceFile ResolveRelativeFile(
+      const Value& p,
+      Err* err,
+      const std::string_view& source_root = std::string_view()) const;
+
+  // Wrapper for ResolveRelativeAs.
+  template <typename StringType>
+  SourceDir ResolveRelativeDir(
+      const Value& blame_input_value,
+      const StringType& input_value,
+      Err* err,
+      const std::string_view& source_root = std::string_view()) const {
+    SourceDir ret;
+    ret.value_ = StringAtom(ResolveRelativeAs<StringType>(
+        false, blame_input_value, input_value, err, source_root));
+    return ret;
+  }
+
+  // Wrapper for ResolveRelativeDir where input_value equals to
+  // v.string_value().
+  SourceDir ResolveRelativeDir(
+      const Value& v,
+      Err* err,
+      const std::string_view& source_root = std::string_view()) const;
+
+  // Resolves this source file relative to some given source root. Returns
+  // an empty file path on error.
+  base::FilePath Resolve(const base::FilePath& source_root) const;
+
+  bool is_null() const { return value_.empty(); }
+  const std::string& value() const { return value_.str(); }
+
+  // Returns true if this path starts with a "//" which indicates a path
+  // from the source root.
+  bool is_source_absolute() const {
+    const std::string& v = value_.str();
+    return v.size() >= 2 && v[0] == '/' && v[1] == '/';
+  }
+
+  // Returns true if this path starts with a single slash which indicates a
+  // system-absolute path.
+  bool is_system_absolute() const { return !is_source_absolute(); }
+
+  // Returns a source-absolute path starting with only one slash at the
+  // beginning (normally source-absolute paths start with two slashes to mark
+  // them as such). This is normally used when concatenating directories
+  // together.
+  //
+  // This function asserts that the directory is actually source-absolute. The
+  // return value points into our buffer.
+  std::string_view SourceAbsoluteWithOneSlash() const {
+    CHECK(is_source_absolute());
+    const std::string& v = value_.str();
+    return std::string_view(&v[1], v.size() - 1);
+  }
+
+  // Returns a path that does not end with a slash.
+  //
+  // This function simply returns the reference to the value if the path is a
+  // root, e.g. "/" or "//".
+  std::string_view SourceWithNoTrailingSlash() const {
+    const std::string& v = value_.str();
+    if (v.size() > 2)
+      return std::string_view(&v[0], v.size() - 1);
+    return std::string_view(v);
+  }
+
+  bool operator==(const SourceDir& other) const {
+    return value_.SameAs(other.value_);
+  }
+  bool operator!=(const SourceDir& other) const { return !operator==(other); }
+  bool operator<(const SourceDir& other) const { return value_ < other.value_; }
+
+  size_t hash() const { return value_.hash(); }
+
+ private:
+  friend class SourceFile;
+  StringAtom value_;
+};
+
+namespace std {
+
+template <>
+struct hash<SourceDir> {
+  std::size_t operator()(const SourceDir& v) const { return v.hash(); }
+};
+
+}  // namespace std
+
+#endif  // TOOLS_GN_SOURCE_DIR_H_
diff --git a/src/gn/source_dir_unittest.cc b/src/gn/source_dir_unittest.cc
new file mode 100644 (file)
index 0000000..148ab92
--- /dev/null
@@ -0,0 +1,208 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/source_dir.h"
+#include "gn/err.h"
+#include "gn/source_file.h"
+#include "gn/value.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(SourceDir, ResolveRelativeFile) {
+  Err err;
+  SourceDir base("//base/");
+#if defined(OS_WIN)
+  std::string_view source_root("C:/source/root");
+#else
+  std::string_view source_root("/source/root");
+#endif
+
+  // Empty input is an error.
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, std::string()), &err,
+                                       source_root) == SourceFile());
+  EXPECT_TRUE(err.has_error());
+
+  // These things are directories, so should be an error.
+  err = Err();
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//foo/bar/"), &err,
+                                       source_root) == SourceFile());
+  EXPECT_TRUE(err.has_error());
+
+  err = Err();
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "bar/"), &err,
+                                       source_root) == SourceFile());
+  EXPECT_TRUE(err.has_error());
+
+  // Absolute paths should be passed unchanged.
+  err = Err();
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//foo"), &err,
+                                       source_root) == SourceFile("//foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "/foo"), &err,
+                                       source_root) == SourceFile("/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  // Basic relative stuff.
+  EXPECT_TRUE(
+      base.ResolveRelativeFile(Value(nullptr, "foo"), &err, source_root) ==
+      SourceFile("//base/foo"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(
+      base.ResolveRelativeFile(Value(nullptr, "./foo"), &err, source_root) ==
+      SourceFile("//base/foo"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "../foo"), &err,
+                                       source_root) == SourceFile("//foo"));
+  EXPECT_FALSE(err.has_error());
+
+// If the given relative path points outside the source root, we
+// expect an absolute path.
+#if defined(OS_WIN)
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "../../foo"), &err,
+                                       source_root) ==
+              SourceFile("/C:/source/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(
+      base.ResolveRelativeFile(Value(nullptr, "//../foo"), &err, source_root) ==
+      SourceFile("/C:/source/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//../root/foo"), &err,
+                                       source_root) ==
+              SourceFile("/C:/source/root/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//../../../foo/bar"),
+                                       &err,
+                                       source_root) == SourceFile("/foo/bar"));
+  EXPECT_FALSE(err.has_error());
+#else
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "../../foo"), &err,
+                                       source_root) ==
+              SourceFile("/source/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(
+      base.ResolveRelativeFile(Value(nullptr, "//../foo"), &err, source_root) ==
+      SourceFile("/source/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//../root/foo"), &err,
+                                       source_root) ==
+              SourceFile("/source/root/foo"));
+  EXPECT_FALSE(err.has_error());
+
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "//../../../foo/bar"),
+                                       &err,
+                                       source_root) == SourceFile("/foo/bar"));
+  EXPECT_FALSE(err.has_error());
+#endif
+
+#if defined(OS_WIN)
+  // Note that we don't canonicalize the backslashes to forward slashes.
+  // This could potentially be changed in the future which would mean we should
+  // just change the expected result.
+  EXPECT_TRUE(base.ResolveRelativeFile(Value(nullptr, "C:\\foo\\bar.txt"), &err,
+                                       source_root) ==
+              SourceFile("/C:/foo/bar.txt"));
+  EXPECT_FALSE(err.has_error());
+#endif
+}
+
+TEST(SourceDir, ResolveRelativeDir) {
+  Err err;
+  SourceDir base("//base/");
+#if defined(OS_WIN)
+  std::string_view source_root("C:/source/root");
+#else
+  std::string_view source_root("/source/root");
+#endif
+
+  // Empty input is an error.
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, std::string()), &err,
+                                      source_root) == SourceDir());
+  EXPECT_TRUE(err.has_error());
+
+  // Absolute paths should be passed unchanged.
+  err = Err();
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "//foo"), &err,
+                                      source_root) == SourceDir("//foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "/foo"), &err,
+                                      source_root) == SourceDir("/foo/"));
+  EXPECT_FALSE(err.has_error());
+
+  // Basic relative stuff.
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "foo"), &err,
+                                      source_root) == SourceDir("//base/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "./foo"), &err,
+                                      source_root) == SourceDir("//base/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "../foo"), &err,
+                                      source_root) == SourceDir("//foo/"));
+  EXPECT_FALSE(err.has_error());
+
+// If the given relative path points outside the source root, we
+// expect an absolute path.
+#if defined(OS_WIN)
+  EXPECT_TRUE(
+      base.ResolveRelativeDir(Value(nullptr, "../../foo"), &err, source_root) ==
+      SourceDir("/C:/source/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(
+      base.ResolveRelativeDir(Value(nullptr, "//../foo"), &err, source_root) ==
+      SourceDir("/C:/source/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "//.."), &err,
+                                      source_root) == SourceDir("/C:/source/"));
+  EXPECT_FALSE(err.has_error());
+#else
+  EXPECT_TRUE(
+      base.ResolveRelativeDir(Value(nullptr, "../../foo"), &err, source_root) ==
+      SourceDir("/source/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(
+      base.ResolveRelativeDir(Value(nullptr, "//../foo"), &err, source_root) ==
+      SourceDir("/source/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "//.."), &err,
+                                      source_root) == SourceDir("/source/"));
+  EXPECT_FALSE(err.has_error());
+#endif
+
+#if defined(OS_WIN)
+  // Canonicalize the existing backslashes to forward slashes and add a
+  // leading slash if necessary.
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "\\C:\\foo"), &err) ==
+              SourceDir("/C:/foo/"));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_TRUE(base.ResolveRelativeDir(Value(nullptr, "C:\\foo"), &err) ==
+              SourceDir("/C:/foo/"));
+  EXPECT_FALSE(err.has_error());
+#endif
+}
+
+TEST(SourceDir, SourceWithNoTrailingSlash) {
+  Err err;
+  SourceDir base("//base/");
+  SourceDir base_no_slash("//base/");
+  EXPECT_EQ(base.SourceWithNoTrailingSlash(), "//base");
+  EXPECT_EQ(base_no_slash.SourceWithNoTrailingSlash(), "//base");
+
+  SourceDir relative_root("//");
+  EXPECT_EQ(relative_root.SourceWithNoTrailingSlash(), "//");
+
+#if defined(OS_WIN)
+  SourceDir root("C:/");
+  SourceDir root_no_slash("C:");
+  EXPECT_EQ(root.SourceWithNoTrailingSlash(), "C:");
+  EXPECT_EQ(root_no_slash.SourceWithNoTrailingSlash(), "C:");
+#else
+  SourceDir root("/");
+  EXPECT_EQ(root.SourceWithNoTrailingSlash(), "/");
+#endif
+}
diff --git a/src/gn/source_file.cc b/src/gn/source_file.cc
new file mode 100644 (file)
index 0000000..52066d7
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/source_file.h"
+
+#include "base/logging.h"
+#include "gn/filesystem_utils.h"
+#include "gn/source_dir.h"
+#include "util/build_config.h"
+
+namespace {
+
+void AssertValueSourceFileString(const std::string& s) {
+#if defined(OS_WIN)
+  DCHECK(s[0] == '/' ||
+         (s.size() > 2 && s[0] != '/' && s[1] == ':' && IsSlash(s[2])));
+#else
+  DCHECK(s[0] == '/');
+#endif
+  DCHECK(!EndsWithSlash(s)) << s;
+}
+
+SourceFile::Type GetSourceFileType(const std::string& file) {
+  std::string_view extension = FindExtension(&file);
+  if (extension == "cc" || extension == "cpp" || extension == "cxx" ||
+      extension == "c++")
+    return SourceFile::SOURCE_CPP;
+  if (extension == "h" || extension == "hpp" || extension == "hxx" ||
+      extension == "hh" || extension == "inc" || extension == "ipp" ||
+      extension == "inl")
+    return SourceFile::SOURCE_H;
+  if (extension == "c")
+    return SourceFile::SOURCE_C;
+  if (extension == "m")
+    return SourceFile::SOURCE_M;
+  if (extension == "mm")
+    return SourceFile::SOURCE_MM;
+  if (extension == "modulemap")
+    return SourceFile::SOURCE_MODULEMAP;
+  if (extension == "rc")
+    return SourceFile::SOURCE_RC;
+  if (extension == "S" || extension == "s" || extension == "asm")
+    return SourceFile::SOURCE_S;
+  if (extension == "o" || extension == "obj")
+    return SourceFile::SOURCE_O;
+  if (extension == "def")
+    return SourceFile::SOURCE_DEF;
+  if (extension == "rs")
+    return SourceFile::SOURCE_RS;
+  if (extension == "go")
+    return SourceFile::SOURCE_GO;
+  if (extension == "swift")
+    return SourceFile::SOURCE_SWIFT;
+  if (extension == "swiftmodule")
+    return SourceFile::SOURCE_SWIFTMODULE;
+
+  return SourceFile::SOURCE_UNKNOWN;
+}
+
+std::string Normalized(std::string value) {
+  DCHECK(!value.empty());
+  AssertValueSourceFileString(value);
+  NormalizePath(&value);
+  return value;
+}
+
+}  // namespace
+
+SourceFile::SourceFile(const std::string& value)
+    : SourceFile(StringAtom(Normalized(value))) {}
+
+SourceFile::SourceFile(std::string&& value)
+    : SourceFile(StringAtom(Normalized(std::move(value)))) {}
+
+SourceFile::SourceFile(StringAtom value) : value_(value) {
+  type_ = GetSourceFileType(value_.str());
+}
+
+std::string SourceFile::GetName() const {
+  if (is_null())
+    return std::string();
+
+  const std::string& value = value_.str();
+  DCHECK(value.find('/') != std::string::npos);
+  size_t last_slash = value.rfind('/');
+  return std::string(&value[last_slash + 1], value.size() - last_slash - 1);
+}
+
+SourceDir SourceFile::GetDir() const {
+  if (is_null())
+    return SourceDir();
+
+  const std::string& value = value_.str();
+  DCHECK(value.find('/') != std::string::npos);
+  size_t last_slash = value.rfind('/');
+  return SourceDir(value.substr(0, last_slash + 1));
+}
+
+base::FilePath SourceFile::Resolve(const base::FilePath& source_root) const {
+  return ResolvePath(value_.str(), true, source_root);
+}
+
+void SourceFile::SetValue(const std::string& value) {
+  value_ = StringAtom(value);
+  type_ = GetSourceFileType(value);
+}
+
+SourceFileTypeSet::SourceFileTypeSet() : empty_(true) {
+  memset(flags_, 0,
+         sizeof(bool) * static_cast<int>(SourceFile::SOURCE_NUMTYPES));
+}
+
+bool SourceFileTypeSet::CSourceUsed() const {
+  return empty_ || Get(SourceFile::SOURCE_CPP) ||
+         Get(SourceFile::SOURCE_MODULEMAP) || Get(SourceFile::SOURCE_H) ||
+         Get(SourceFile::SOURCE_C) || Get(SourceFile::SOURCE_M) ||
+         Get(SourceFile::SOURCE_MM) || Get(SourceFile::SOURCE_RC) ||
+         Get(SourceFile::SOURCE_S) || Get(SourceFile::SOURCE_O) ||
+         Get(SourceFile::SOURCE_DEF);
+}
+
+bool SourceFileTypeSet::RustSourceUsed() const {
+  return Get(SourceFile::SOURCE_RS);
+}
+
+bool SourceFileTypeSet::GoSourceUsed() const {
+  return Get(SourceFile::SOURCE_GO);
+}
+
+bool SourceFileTypeSet::SwiftSourceUsed() const {
+  return Get(SourceFile::SOURCE_SWIFT);
+}
+
+bool SourceFileTypeSet::MixedSourceUsed() const {
+  return (1 << static_cast<int>(CSourceUsed())
+            << static_cast<int>(RustSourceUsed())
+            << static_cast<int>(GoSourceUsed())
+            << static_cast<int>(SwiftSourceUsed())) > 2;
+}
diff --git a/src/gn/source_file.h b/src/gn/source_file.h
new file mode 100644 (file)
index 0000000..5ad8f62
--- /dev/null
@@ -0,0 +1,172 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SOURCE_FILE_H_
+#define TOOLS_GN_SOURCE_FILE_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+
+#include "gn/string_atom.h"
+
+class SourceDir;
+
+// Represents a file within the source tree. Always begins in a slash, never
+// ends in one.
+class SourceFile {
+ public:
+  // This should be sequential integers starting from 0 so they can be used as
+  // array indices.
+  enum Type {
+    SOURCE_UNKNOWN = 0,
+    SOURCE_ASM,
+    SOURCE_C,
+    SOURCE_CPP,
+    SOURCE_H,
+    SOURCE_M,
+    SOURCE_MM,
+    SOURCE_MODULEMAP,
+    SOURCE_S,
+    SOURCE_RC,
+    SOURCE_O,  // Object files can be inputs, too. Also counts .obj.
+    SOURCE_DEF,
+
+    SOURCE_RS,
+    SOURCE_GO,
+    SOURCE_SWIFT,
+    SOURCE_SWIFTMODULE,
+
+    // Must be last.
+    SOURCE_NUMTYPES,
+  };
+
+  SourceFile() = default;
+
+  // Takes a known absolute source file. Always begins in a slash.
+  explicit SourceFile(const std::string& value);
+  explicit SourceFile(std::string&& value);
+  explicit SourceFile(StringAtom value);
+
+  ~SourceFile() = default;
+
+  bool is_null() const { return value_.empty(); }
+  const std::string& value() const { return value_.str(); }
+  Type type() const { return type_; }
+
+  // Returns everything after the last slash.
+  std::string GetName() const;
+  SourceDir GetDir() const;
+
+  // Resolves this source file relative to some given source root. Returns
+  // an empty file path on error.
+  base::FilePath Resolve(const base::FilePath& source_root) const;
+
+  // Returns true if this file starts with a "//" which indicates a path
+  // from the source root.
+  bool is_source_absolute() const {
+    return value().size() >= 2 && value()[0] == '/' && value()[1] == '/';
+  }
+
+  // Returns true if this file starts with a single slash which indicates a
+  // system-absolute path.
+  bool is_system_absolute() const { return !is_source_absolute(); }
+
+  // Returns a source-absolute path starting with only one slash at the
+  // beginning (normally source-absolute paths start with two slashes to mark
+  // them as such). This is normally used when concatenating names together.
+  //
+  // This function asserts that the file is actually source-absolute. The
+  // return value points into our buffer.
+  std::string_view SourceAbsoluteWithOneSlash() const {
+    CHECK(is_source_absolute());
+    return std::string_view(&value()[1], value().size() - 1);
+  }
+
+  bool operator==(const SourceFile& other) const {
+    return value_ == other.value_;
+  }
+  bool operator!=(const SourceFile& other) const { return !operator==(other); }
+  bool operator<(const SourceFile& other) const {
+    return value_ < other.value_;
+  }
+
+  struct PtrCompare {
+    bool operator()(const SourceFile& a, const SourceFile& b) const noexcept {
+      return StringAtom::PtrCompare()(a.value_, b.value_);
+    }
+  };
+  struct PtrHash {
+    size_t operator()(const SourceFile& s) const noexcept {
+      return StringAtom::PtrHash()(s.value_);
+    }
+  };
+
+  struct PtrEqual {
+    bool operator()(const SourceFile& a, const SourceFile& b) const noexcept {
+      return StringAtom::PtrEqual()(a.value_, b.value_);
+    }
+  };
+
+ private:
+  friend class SourceDir;
+
+  void SetValue(const std::string& value);
+
+  StringAtom value_;
+  Type type_ = SOURCE_UNKNOWN;
+};
+
+namespace std {
+
+template <>
+struct hash<SourceFile> {
+  std::size_t operator()(const SourceFile& v) const {
+    hash<std::string> h;
+    return h(v.value());
+  }
+};
+
+}  // namespace std
+
+// Represents a set of source files.
+// NOTE: In practice, this is much faster than using an std::set<> or
+// std::unordered_set<> container. E.g. for the Fuchsia Zircon build, the
+// overall difference in "gn gen" time is about 10%.
+using SourceFileSet = base::flat_set<SourceFile, SourceFile::PtrCompare>;
+
+// Represents a set of tool types.
+class SourceFileTypeSet {
+ public:
+  SourceFileTypeSet();
+
+  void Set(SourceFile::Type type) {
+    flags_[static_cast<int>(type)] = true;
+    empty_ = false;
+  }
+  bool Get(SourceFile::Type type) const {
+    return flags_[static_cast<int>(type)];
+  }
+
+  bool empty() const { return empty_; }
+
+  bool CSourceUsed() const;
+  bool RustSourceUsed() const;
+  bool GoSourceUsed() const;
+  bool SwiftSourceUsed() const;
+
+  bool MixedSourceUsed() const;
+
+ private:
+  bool empty_;
+  bool flags_[static_cast<int>(SourceFile::SOURCE_NUMTYPES)];
+};
+
+#endif  // TOOLS_GN_SOURCE_FILE_H_
diff --git a/src/gn/source_file_unittest.cc b/src/gn/source_file_unittest.cc
new file mode 100644 (file)
index 0000000..9d3a123
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/source_file.h"
+
+#include "util/test/test.h"
+
+// The SourceFile object should normalize the input passed to the constructor.
+// The normalizer unit test checks for all the weird edge cases for normalizing
+// so here just check that it gets called.
+TEST(SourceFile, Normalize) {
+  SourceFile a("//foo/../bar.cc");
+  EXPECT_EQ("//bar.cc", a.value());
+
+  std::string b_str("//foo/././../bar.cc");
+  SourceFile b(std::move(b_str));
+  EXPECT_TRUE(b_str.empty());  // Should have been swapped in.
+  EXPECT_EQ("//bar.cc", b.value());
+}
diff --git a/src/gn/standard_out.cc b/src/gn/standard_out.cc
new file mode 100644 (file)
index 0000000..5564a0c
--- /dev/null
@@ -0,0 +1,342 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/standard_out.h"
+
+#include <stddef.h>
+
+#include <string_view>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "gn/switches.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#else
+#include <stdio.h>
+#include <unistd.h>
+#endif
+
+namespace {
+
+bool initialized = false;
+
+#if defined(OS_WIN)
+HANDLE hstdout;
+WORD default_attributes;
+#endif
+bool is_console = false;
+
+bool is_markdown = false;
+
+// True while output is going into a markdown ```...``` code block.
+bool in_body = false;
+
+void EnsureInitialized() {
+  if (initialized)
+    return;
+  initialized = true;
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  if (cmdline->HasSwitch(switches::kMarkdown)) {
+    // Output help in Markdown's syntax, not color-highlighted.
+    is_markdown = true;
+  }
+
+  if (cmdline->HasSwitch(switches::kNoColor)) {
+    // Force color off.
+    is_console = false;
+    return;
+  }
+
+#if defined(OS_WIN)
+  // On Windows, we can't force the color on. If the output handle isn't a
+  // console, there's nothing we can do about it.
+  hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE);
+  CONSOLE_SCREEN_BUFFER_INFO info;
+  is_console = !!::GetConsoleScreenBufferInfo(hstdout, &info);
+  default_attributes = info.wAttributes;
+#else
+  if (cmdline->HasSwitch(switches::kColor))
+    is_console = true;
+  else
+    is_console = isatty(fileno(stdout));
+#endif
+}
+
+#if !defined(OS_WIN)
+void WriteToStdOut(const std::string& output) {
+  size_t written_bytes = fwrite(output.data(), 1, output.size(), stdout);
+  DCHECK_EQ(output.size(), written_bytes);
+}
+#endif  // !defined(OS_WIN)
+
+void OutputMarkdownDec(TextDecoration dec) {
+  // The markdown rendering turns "dim" text to italics and any
+  // other colored text to bold.
+
+#if defined(OS_WIN)
+  DWORD written = 0;
+  if (dec == DECORATION_DIM)
+    ::WriteFile(hstdout, "*", 1, &written, nullptr);
+  else if (dec != DECORATION_NONE)
+    ::WriteFile(hstdout, "**", 2, &written, nullptr);
+#else
+  if (dec == DECORATION_DIM)
+    WriteToStdOut("*");
+  else if (dec != DECORATION_NONE)
+    WriteToStdOut("**");
+#endif
+}
+
+}  // namespace
+
+#if defined(OS_WIN)
+
+void OutputString(const std::string& output,
+                  TextDecoration dec,
+                  HtmlEscaping escaping) {
+  EnsureInitialized();
+  DWORD written = 0;
+
+  if (is_markdown) {
+    OutputMarkdownDec(dec);
+  } else if (is_console) {
+    switch (dec) {
+      case DECORATION_NONE:
+        break;
+      case DECORATION_DIM:
+        ::SetConsoleTextAttribute(hstdout, FOREGROUND_INTENSITY);
+        break;
+      case DECORATION_RED:
+        ::SetConsoleTextAttribute(hstdout,
+                                  FOREGROUND_RED | FOREGROUND_INTENSITY);
+        break;
+      case DECORATION_GREEN:
+        // Keep green non-bold.
+        ::SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN);
+        break;
+      case DECORATION_BLUE:
+        ::SetConsoleTextAttribute(hstdout,
+                                  FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+        break;
+      case DECORATION_YELLOW:
+        ::SetConsoleTextAttribute(hstdout, FOREGROUND_RED | FOREGROUND_GREEN);
+        break;
+    }
+  }
+
+  std::string tmpstr = output;
+  if (is_markdown && dec == DECORATION_YELLOW) {
+    // https://code.google.com/p/gitiles/issues/detail?id=77
+    // Gitiles will replace "--" with an em dash in non-code text.
+    // Figuring out all instances of this might be difficult, but we can
+    // at least escape the instances where this shows up in a heading.
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
+  }
+  if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
+    // Markdown auto-escapes < and > in code sections (and converts &lt; to
+    // &amp;tl; there), but not elsewhere.
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "&lt;");
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", "&gt;");
+  }
+  ::WriteFile(hstdout, tmpstr.c_str(), static_cast<DWORD>(tmpstr.size()),
+              &written, nullptr);
+
+  if (is_markdown) {
+    OutputMarkdownDec(dec);
+  } else if (is_console) {
+    ::SetConsoleTextAttribute(hstdout, default_attributes);
+  }
+}
+
+#else
+
+void OutputString(const std::string& output,
+                  TextDecoration dec,
+                  HtmlEscaping escaping) {
+  EnsureInitialized();
+  if (is_markdown) {
+    OutputMarkdownDec(dec);
+  } else if (is_console) {
+    switch (dec) {
+      case DECORATION_NONE:
+        break;
+      case DECORATION_DIM:
+        WriteToStdOut("\e[2m");
+        break;
+      case DECORATION_RED:
+        WriteToStdOut("\e[31m\e[1m");
+        break;
+      case DECORATION_GREEN:
+        WriteToStdOut("\e[32m");
+        break;
+      case DECORATION_BLUE:
+        WriteToStdOut("\e[34m\e[1m");
+        break;
+      case DECORATION_YELLOW:
+        WriteToStdOut("\e[33m");
+        break;
+    }
+  }
+
+  std::string tmpstr = output;
+  if (is_markdown && dec == DECORATION_YELLOW) {
+    // https://code.google.com/p/gitiles/issues/detail?id=77
+    // Gitiles will replace "--" with an em dash in non-code text.
+    // Figuring out all instances of this might be difficult, but we can
+    // at least escape the instances where this shows up in a heading.
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "--", "\\--");
+  }
+  if (is_markdown && !in_body && escaping == DEFAULT_ESCAPING) {
+    // Markdown auto-escapes < and > in code sections (and converts &lt; to
+    // &amp;tl; there), but not elsewhere.
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, "<", "&lt;");
+    base::ReplaceSubstringsAfterOffset(&tmpstr, 0, ">", "&gt;");
+  }
+  WriteToStdOut(tmpstr.data());
+
+  if (is_markdown) {
+    OutputMarkdownDec(dec);
+  } else if (is_console && dec != DECORATION_NONE) {
+    WriteToStdOut("\e[0m");
+  }
+}
+
+#endif
+
+void PrintSectionHelp(const std::string& line,
+                      const std::string& topic,
+                      const std::string& tag) {
+  EnsureInitialized();
+
+  if (is_markdown) {
+    OutputString("*   [" + line + "](#" + tag + ")\n");
+  } else if (topic.size()) {
+    OutputString("\n" + line + " (type \"gn help " + topic +
+                 "\" for more help):\n");
+  } else {
+    OutputString("\n" + line + ":\n");
+  }
+}
+
+void PrintShortHelp(const std::string& line, const std::string& link_tag) {
+  EnsureInitialized();
+
+  if (is_markdown) {
+    if (link_tag.empty())
+      OutputString("    *   " + line + "\n");
+    else
+      OutputString("    *   [" + line + "](#" + link_tag + ")\n");
+    return;
+  }
+
+  size_t colon_offset = line.find(':');
+  size_t first_normal = 0;
+  if (colon_offset != std::string::npos) {
+    OutputString("  " + line.substr(0, colon_offset), DECORATION_YELLOW);
+    first_normal = colon_offset;
+  }
+
+  // See if the colon is followed by a " [" and if so, dim the contents of [ ].
+  if (first_normal > 0 && line.size() > first_normal + 2 &&
+      line[first_normal + 1] == ' ' && line[first_normal + 2] == '[') {
+    size_t begin_bracket = first_normal + 2;
+    OutputString(": ");
+    first_normal = line.find(']', begin_bracket);
+    if (first_normal == std::string::npos)
+      first_normal = line.size();
+    else
+      first_normal++;
+    OutputString(line.substr(begin_bracket, first_normal - begin_bracket),
+                 DECORATION_DIM);
+  }
+
+  OutputString(line.substr(first_normal) + "\n");
+}
+
+void PrintLongHelp(const std::string& text, const std::string& tag) {
+  EnsureInitialized();
+
+  bool first_header = true;
+  in_body = false;
+  std::size_t empty_lines = 0;
+  for (const std::string& line : base::SplitString(
+           text, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    // Check for a heading line.
+    if (!line.empty() && line[0] != ' ') {
+      // New paragraph, just skip any trailing empty lines.
+      empty_lines = 0;
+
+      if (is_markdown) {
+        // GN's block-level formatting is converted to markdown as follows:
+        // * The first heading is treated as an H3.
+        // * Subsequent heading are treated as H4s.
+        // * Any other text is wrapped in a code block and displayed as-is.
+        //
+        // Span-level formatting (the decorations) is converted inside
+        // OutputString().
+        if (in_body) {
+          OutputString("```\n\n", DECORATION_NONE);
+          in_body = false;
+        }
+
+        if (first_header && !tag.empty()) {
+          OutputString("### <a name=\"" + tag + "\"></a>", DECORATION_NONE,
+                       NO_ESCAPING);
+          first_header = false;
+        } else {
+          OutputString("#### ", DECORATION_NONE);
+        }
+      }
+
+      // Highlight up to the colon (if any).
+      size_t chars_to_highlight = line.find(':');
+      if (chars_to_highlight == std::string::npos)
+        chars_to_highlight = line.size();
+
+      OutputString(line.substr(0, chars_to_highlight), DECORATION_YELLOW);
+      OutputString(line.substr(chars_to_highlight));
+      OutputString("\n");
+      continue;
+    } else if (is_markdown && !line.empty() && !in_body) {
+      OutputString("```\n", DECORATION_NONE);
+      in_body = true;
+    }
+
+    // We buffer empty lines, so we can skip them if needed
+    // (i.e. new paragraph body, end of final paragraph body).
+    if (in_body && is_markdown) {
+      if (!line.empty() && empty_lines != 0) {
+        OutputString(std::string(empty_lines, '\n'));
+        empty_lines = 0;
+      } else if (line.empty()) {
+        ++empty_lines;
+        continue;
+      }
+    }
+
+    // Check for a comment.
+    TextDecoration dec = DECORATION_NONE;
+    for (const auto& elem : line) {
+      if (elem == '#' && !is_markdown) {
+        // Got a comment, draw dimmed.
+        dec = DECORATION_DIM;
+        break;
+      } else if (elem != ' ') {
+        break;
+      }
+    }
+
+    OutputString(line + "\n", dec);
+  }
+
+  if (is_markdown && in_body)
+    OutputString("```\n");
+}
diff --git a/src/gn/standard_out.h b/src/gn/standard_out.h
new file mode 100644 (file)
index 0000000..5c0fcce
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_STANDARD_OUT_H_
+#define TOOLS_GN_STANDARD_OUT_H_
+
+#include <string>
+
+enum TextDecoration {
+  DECORATION_NONE = 0,
+  DECORATION_DIM,
+  DECORATION_RED,
+  DECORATION_GREEN,
+  DECORATION_BLUE,
+  DECORATION_YELLOW
+};
+
+enum HtmlEscaping {
+  NO_ESCAPING,
+
+  // Convert < and > to &lt; and &gt; when writing markdown output in non-code
+  // sections.
+  DEFAULT_ESCAPING,
+};
+
+void OutputString(const std::string& output,
+                  TextDecoration dec = DECORATION_NONE,
+                  HtmlEscaping = DEFAULT_ESCAPING);
+
+// If printing markdown, this generates table-of-contents entries with
+// links to the actual help; otherwise, prints a one-line description.
+void PrintSectionHelp(const std::string& line,
+                      const std::string& topic,
+                      const std::string& tag);
+
+// Prints a line for a command, assuming there is a colon. Everything before
+// the colon is the command (and is highlighted). After the colon if there is
+// a square bracket, the contents of the bracket is dimmed.
+//
+// The link_tag is set, it will be used for markdown output links. This is
+// used when generating the markdown for all help topics. If empty, no link tag
+// will be emitted. In non-markdown mode, this parameter will be ignored.
+//
+// The line is indented 2 spaces.
+void PrintShortHelp(const std::string& line,
+                    const std::string& link_tag = std::string());
+
+// Prints a longer help section.
+//
+// Rules:
+// - Lines beginning with non-whitespace are highlighted up to the first
+//   colon (or the whole line if not).
+// - Lines whose first non-whitespace character is a # are dimmed.
+//
+// The tag will be used as a link target for the first header. This is used
+// when generating the markdown for all help topics. If empty, no link tag will
+// be emitted. Used only in markdown mode.
+void PrintLongHelp(const std::string& text, const std::string& tag = "");
+
+#endif  // TOOLS_GN_STANDARD_OUT_H_
diff --git a/src/gn/string_atom.cc b/src/gn/string_atom.cc
new file mode 100644 (file)
index 0000000..fe1ef73
--- /dev/null
@@ -0,0 +1,215 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_atom.h"
+
+#include <array>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gn/hash_table_base.h"
+
+namespace {
+
+// Implementation note:
+//
+// StringAtomSet implements the global shared state, which is:
+//
+//    - a group of std::string instances with a persistent address, allocated
+//      through a fast slab allocator.
+//
+//    - a set of string pointers, corresponding to the known strings in the
+//      group.
+//
+//    - a mutex to ensure correct thread-safety.
+//
+//    - a find() method that takes an std::string_view argument, and uses it
+//      to find a matching entry in the string tree. If none is available,
+//      a new std::string is allocated and its address inserted into the tree
+//      before being returned.
+//
+// Because the mutex is a large bottleneck, each thread implements
+// its own local string pointer cache, and will only call StringAtomSet::find()
+// in case of a lookup miss. This is critical for good performance.
+//
+
+static const std::string kEmptyString;
+
+using KeyType = const std::string*;
+
+// A HashTableBase node type that stores one hash value and one string pointer.
+struct KeyNode {
+  size_t hash;
+  KeyType key;
+
+  // The following methods are required by HashTableBase<>
+  bool is_valid() const { return !is_null(); }
+  bool is_null() const { return !key; }
+  size_t hash_value() const { return hash; }
+
+  // No deletion support means faster lookup code.
+  static constexpr bool is_tombstone() { return false; }
+};
+
+// This is a trivial hash table of string pointers, using open addressing.
+// It is faster in practice than using a standard container or even a
+// base::flat_set<>.
+//
+// Usage is the following:
+//
+//     1) Compute string hash value.
+//
+//     2) Call Lookup() with the hash value and the string_view key,
+//        this always returns a mutable Node* pointer, say |node|.
+//
+//     3) If |node->key| is not nullptr, this is the key to use.
+//        Otherwise, allocate a new string with persistent address,
+//        and call Insert(), passing the |node|, |hash| and new string
+//        address as arguments.
+//
+struct KeySet : public HashTableBase<KeyNode> {
+  using BaseType = HashTableBase<KeyNode>;
+  using Node = BaseType::Node;
+
+  // Compute hash for |str|. Replace with faster hash function if available.
+  static size_t Hash(std::string_view str) {
+    return std::hash<std::string_view>()(str);
+  }
+
+  // Lookup for |str| with specific |hash| value.
+  // Return a Node pointer. If the key was found, |node.key| is its value.
+  // Otherwise, the caller should create a new key value, then call Insert()
+  // below.
+  //
+  // NOTE: Even though this method is const, because it doesn't modify the
+  //       state of the KeySet, it returns a *mutable* node pointer, to be
+  //       passed to Insert() in case of a miss.
+  //
+  Node* Lookup(size_t hash, std::string_view str) const {
+    return BaseType::NodeLookup(hash, [hash, &str](const Node* node) {
+      // NOTE: Only is_valid() node pointers are passed to this function
+      // which means key won't be null, and there are no tombstone values
+      // in this derivation of HashTableBase<>.
+      return node->hash == hash && *node->key == str;
+    });
+  }
+
+  void Insert(Node* node, size_t hash, KeyType key) {
+    node->hash = hash;
+    node->key = key;
+    BaseType::UpdateAfterInsert();
+  }
+};
+
+class StringAtomSet {
+ public:
+  StringAtomSet() {
+    // Ensure kEmptyString is in our set while not being allocated
+    // from a slab. The end result is that find("") should always
+    // return this address.
+    //
+    // This allows the StringAtom() default initializer to use the same
+    // address directly, avoiding a table lookup.
+    //
+    size_t hash = set_.Hash("");
+    auto* node = set_.Lookup(hash, "");
+    set_.Insert(node, hash, &kEmptyString);
+  }
+
+  // Find the unique constant string pointer for |key|.
+  const std::string* find(std::string_view key) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    size_t hash = set_.Hash(key);
+    auto* node = set_.Lookup(hash, key);
+    if (node->key)
+      return node->key;
+
+    // Allocate new string, insert its address in the set.
+    if (slab_index_ >= kStringsPerSlab) {
+      slabs_.push_back(new Slab());
+      slab_index_ = 0;
+    }
+    std::string* result = slabs_.back()->init(slab_index_++, key);
+    set_.Insert(node, hash, result);
+    return result;
+  }
+
+ private:
+  static constexpr unsigned int kStringsPerSlab = 128;
+
+  // Each slab is allocated independently, has a fixed address and stores
+  // kStringsPerSlab items of type StringStorage. The latter has the same
+  // size and alignment as std::string, but doesn't need default-initialization.
+  // This is used to slightly speed up Slab allocation and string
+  // initialization. The drawback is that on process exit, allocated strings
+  // are leaked (but GN already leaks several hundred MiBs of memory anyway).
+
+  // A C++ union that can store an std::string but without default
+  // initialization and destruction.
+  union StringStorage {
+    StringStorage() {}
+    ~StringStorage() {}
+    char dummy;
+    std::string str;
+  };
+
+  // A fixed array of StringStorage items. Can be allocated cheaply.
+  class Slab {
+   public:
+    // Init the n-th string in the slab with |str|.
+    // Return its location as well.
+    std::string* init(size_t index, const std::string_view& str) {
+      std::string* result = &items_[index].str;
+      new (result) std::string(str);
+      return result;
+    }
+
+   private:
+    StringStorage items_[kStringsPerSlab];
+  };
+
+  std::mutex mutex_;
+  KeySet set_;
+  std::vector<Slab*> slabs_;
+  unsigned int slab_index_ = kStringsPerSlab;
+};
+
+StringAtomSet& GetStringAtomSet() {
+  static StringAtomSet s_string_atom_set;
+  return s_string_atom_set;
+}
+
+// Each thread maintains its own ThreadLocalCache to perform fast lookups
+// without taking any mutex in most cases.
+class ThreadLocalCache {
+ public:
+  // Find the unique constant string pointer for |key| in this cache,
+  // and fallback to the global one in case of a miss.
+  KeyType find(std::string_view key) {
+    size_t hash = local_set_.Hash(key);
+    auto* node = local_set_.Lookup(hash, key);
+    if (node->key)
+      return node->key;
+
+    KeyType result = GetStringAtomSet().find(key);
+    local_set_.Insert(node, hash, result);
+    return result;
+  }
+
+ private:
+  KeySet local_set_;
+};
+
+thread_local ThreadLocalCache s_local_cache;
+
+}  // namespace
+
+StringAtom::StringAtom() : value_(kEmptyString) {}
+
+StringAtom::StringAtom(std::string_view str) noexcept
+    : value_(*s_local_cache.find(str)) {}
diff --git a/src/gn/string_atom.h b/src/gn/string_atom.h
new file mode 100644 (file)
index 0000000..29d4e41
--- /dev/null
@@ -0,0 +1,179 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_STRING_ATOM_H_
+#define TOOLS_GN_STRING_ATOM_H_
+
+#include <functional>
+#include <string>
+
+// A StringAtom models a pointer to a globally unique constant string.
+//
+// They are useful as key types for sets and map container types, especially
+// when a program uses multiple instances that tend to use the same strings
+// (as happen very frequently in GN).
+//
+// Note that default equality and comparison functions will compare the
+// string content, not the pointers, ensuring that the behaviour of
+// standard containers using StringAtom key types is the same as if
+// std::string was used.
+//
+// In addition, _ordered_ containers support heterogeneous lookups (i.e.
+// using an std::string_view, and by automatic conversion, a const char*
+// of const char[] literal) as a key type.
+//
+// Additionally, it is also possible to implement very fast _unordered_
+// containers by using the StringAtom::Fast{Hash,Equal,Compare} structs,
+// which will force containers to hash/compare pointer values instead,
+// for example:
+//
+//     // A fast unordered set of unique strings.
+//     //
+//     // Implementation uses a hash table so performance will be bounded
+//     // by the string hash function. Does not support heterogeneous lookups.
+//     //
+//     using FastStringSet = std::unordered_set<StringAtom,
+//                                              StringAtom::PtrHash,
+//                                              StringAtom::PtrEqual>;
+//
+//     // A fast unordered set of unique strings.
+//     //
+//     // Implementation uses a balanced binary tree so performance will
+//     // be bounded by string comparisons. Does support heterogeneous lookups,
+//     // but not that this does not extend to the [] operator, only to the
+//     // find() method.
+//     //
+//     using FastStringSet = std::set<StringAtom, StringAtom::PtrCompare>
+//
+//     // A fast unordered { string -> VALUE } map.
+//     //
+//     // Implementation uses a balanced binary tree. Supports heterogeneous
+//     // lookups.
+//     template <typename VALUE>
+//     using FastStringMap = std::map<StringAtom, VALUE, StringAtom::PtrCompare>
+//
+class StringAtom {
+ public:
+  // Default constructor. Value points to a globally unique empty string.
+  StringAtom();
+
+  // Destructor should do nothing at all.
+  ~StringAtom() = default;
+
+  // Non-explicit constructors.
+  StringAtom(std::string_view str) noexcept;
+
+  // Copy and move operations.
+  StringAtom(const StringAtom& other) noexcept : value_(other.value_) {}
+  StringAtom& operator=(const StringAtom& other) noexcept {
+    if (this != &other) {
+      this->~StringAtom();  // really a no-op
+      new (this) StringAtom(other);
+    }
+    return *this;
+  }
+
+  StringAtom(StringAtom&& other) noexcept : value_(other.value_) {}
+  StringAtom& operator=(const StringAtom&& other) noexcept {
+    if (this != &other) {
+      this->~StringAtom();  // really a no-op
+      new (this) StringAtom(std::move(other));
+    }
+    return *this;
+  }
+
+  bool empty() const { return value_.empty(); }
+
+  // Explicit conversions.
+  const std::string& str() const { return value_; }
+
+  // Implicit conversions.
+  operator std::string_view() const { return {value_}; }
+
+  // Returns true iff this is the same key.
+  // Note that the default comparison functions compare the value instead
+  // in order to use them in standard containers without surprises by
+  // default.
+  bool SameAs(const StringAtom& other) const {
+    return &value_ == &other.value_;
+  }
+
+  // Default comparison functions.
+  bool operator==(const StringAtom& other) const {
+    return value_ == other.value_;
+  }
+
+  bool operator!=(const StringAtom& other) const {
+    return value_ != other.value_;
+  }
+
+  bool operator<(const StringAtom& other) const {
+    // Avoid one un-necessary string comparison if values are equal.
+    if (SameAs(other))
+      return false;
+
+    return value_ < other.value_;
+  }
+
+  size_t hash() const { return std::hash<std::string>()(value_); }
+
+  // Use the following structs to implement containers that use StringAtom
+  // values as keys, but only compare/hash the pointer values for speed.
+  // E.g.:
+  //    using FastSet = std::unordered_set<StringAtom, PtrHash, PtrEqual>;
+  //    using FastMap = std::map<StringAtom, Value, PtrCompare>;
+  //
+  // IMPORTANT: Note that FastMap above is ordered based in the StringAtom
+  //            pointer value, not the string content.
+  //
+  struct PtrHash {
+    size_t operator()(const StringAtom& key) const noexcept {
+      return std::hash<const std::string*>()(&key.value_);
+    }
+  };
+
+  struct PtrEqual {
+    bool operator()(const StringAtom& a, const StringAtom& b) const noexcept {
+      return &a.value_ == &b.value_;
+    }
+  };
+
+  struct PtrCompare {
+    bool operator()(const StringAtom& a, const StringAtom& b) const noexcept {
+      return &a.value_ < &b.value_;
+    }
+  };
+
+ protected:
+  const std::string& value_;
+};
+
+namespace std {
+
+// Ensure default heterogeneous lookups with other types like std::string_view.
+template <>
+struct less<StringAtom> {
+  using is_transparent = int;
+
+  bool operator()(const StringAtom& a, const StringAtom& b) const noexcept {
+    return a.str() < b.str();
+  }
+  template <typename U>
+  bool operator()(const StringAtom& a, const U& b) const noexcept {
+    return a.str() < b;
+  }
+  template <typename U>
+  bool operator()(const U& a, const StringAtom& b) const noexcept {
+    return a < b.str();
+  };
+};
+
+template <>
+struct hash<StringAtom> {
+  size_t operator()(const StringAtom& key) const noexcept { return key.hash(); }
+};
+
+}  // namespace std
+
+#endif  // TOOLS_GN_STRING_ATOM_H_
diff --git a/src/gn/string_atom_unittest.cc b/src/gn/string_atom_unittest.cc
new file mode 100644 (file)
index 0000000..ba151e3
--- /dev/null
@@ -0,0 +1,148 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_atom.h"
+
+#include "util/test/test.h"
+
+#include <algorithm>
+#include <array>
+#include <set>
+#include <string>
+#include <vector>
+
+TEST(StringAtomTest, EmptyString) {
+  StringAtom key1;
+  StringAtom key2("");
+
+  ASSERT_STREQ(key1.str().c_str(), "");
+  ASSERT_STREQ(key2.str().c_str(), "");
+  ASSERT_EQ(&key1.str(), &key2.str());
+}
+
+TEST(StringAtomTest, Find) {
+  StringAtom empty;
+  EXPECT_EQ(empty.str(), std::string());
+
+  StringAtom foo("foo");
+  EXPECT_EQ(foo.str(), std::string("foo"));
+
+  StringAtom foo2("foo");
+  EXPECT_EQ(&foo.str(), &foo2.str());
+}
+
+// Default compare should always be ordered.
+TEST(StringAtomTest, DefaultCompare) {
+  auto foo = StringAtom("foo");
+  auto bar = StringAtom("bar");
+  auto zoo = StringAtom("zoo");
+
+  EXPECT_TRUE(bar < foo);
+  EXPECT_TRUE(foo < zoo);
+  EXPECT_TRUE(bar < zoo);
+}
+
+TEST(StringAtomTest, NormalSet) {
+  std::set<StringAtom> set;
+  auto foo_ret = set.insert(std::string_view("foo"));
+  auto bar_ret = set.insert(std::string_view("bar"));
+  auto zoo_ret = set.insert(std::string_view("zoo"));
+
+  StringAtom foo_key("foo");
+  EXPECT_EQ(*foo_ret.first, foo_key);
+
+  auto foo_it = set.find(foo_key);
+  EXPECT_NE(foo_it, set.end());
+  EXPECT_EQ(*foo_it, foo_key);
+
+  EXPECT_EQ(set.find(std::string_view("bar")), bar_ret.first);
+  EXPECT_EQ(set.find(std::string_view("zoo")), zoo_ret.first);
+
+  // Normal sets are always ordered according to the key value.
+  auto it = set.begin();
+  EXPECT_EQ(it, bar_ret.first);
+  ++it;
+
+  EXPECT_EQ(it, foo_ret.first);
+  ++it;
+
+  EXPECT_EQ(it, zoo_ret.first);
+  ++it;
+
+  EXPECT_EQ(it, set.end());
+}
+
+TEST(StringAtomTest, FastSet) {
+  std::set<StringAtom, StringAtom::PtrCompare> set;
+
+  auto foo_ret = set.insert(std::string_view("foo"));
+  auto bar_ret = set.insert(std::string_view("bar"));
+  auto zoo_ret = set.insert(std::string_view("zoo"));
+
+  auto atom_to_ptr = [](const StringAtom& atom) -> const std::string* {
+    return &atom.str();
+  };
+
+  EXPECT_TRUE(foo_ret.second);
+  EXPECT_TRUE(bar_ret.second);
+  EXPECT_TRUE(zoo_ret.second);
+
+  const std::string* foo_ptr = atom_to_ptr(*foo_ret.first);
+  const std::string* bar_ptr = atom_to_ptr(*bar_ret.first);
+  const std::string* zoo_ptr = atom_to_ptr(*zoo_ret.first);
+
+  StringAtom foo_key("foo");
+  EXPECT_EQ(foo_ptr, atom_to_ptr(foo_key));
+
+  auto foo_it = set.find(foo_key);
+  EXPECT_NE(foo_it, set.end());
+  EXPECT_EQ(*foo_it, foo_key);
+
+  EXPECT_EQ(set.find(std::string_view("bar")), bar_ret.first);
+  EXPECT_EQ(set.find(std::string_view("zoo")), zoo_ret.first);
+
+  // Fast sets are ordered according to the key pointer.
+  // Even though a bump allocator is used to allocate AtomString
+  // strings, there is no guarantee that the global StringAtom
+  // set was not already populated by a different test previously,
+  // which means the pointers value need to be sorted before
+  // iterating over the set for comparison.
+  std::array<const std::string*, 3> ptrs = {
+      foo_ptr,
+      bar_ptr,
+      zoo_ptr,
+  };
+  std::sort(ptrs.begin(), ptrs.end());
+
+  auto it = set.begin();
+  EXPECT_EQ(atom_to_ptr(*it), ptrs[0]);
+  ++it;
+
+  EXPECT_EQ(atom_to_ptr(*it), ptrs[1]);
+  ++it;
+
+  EXPECT_EQ(atom_to_ptr(*it), ptrs[2]);
+  ++it;
+
+  EXPECT_EQ(it, set.end());
+}
+
+TEST(StringAtom, AllocMoreThanASingleSlabOfKeys) {
+  // Verify that allocating more than 128 string keys works properly.
+  const size_t kMaxCount = 16384;
+  std::vector<StringAtom> keys;
+
+  // Small lambda to create a string for the n-th key.
+  auto string_for = [](size_t index) -> std::string {
+    return std::to_string(index) + "_key";
+  };
+
+  for (size_t nn = 0; nn < kMaxCount; ++nn) {
+    keys.push_back(StringAtom(string_for(nn)));
+  }
+
+  for (size_t nn = 0; nn < kMaxCount; ++nn) {
+    ASSERT_EQ(keys[nn].str(), string_for(nn));
+  }
+}
diff --git a/src/gn/string_output_buffer.cc b/src/gn/string_output_buffer.cc
new file mode 100644 (file)
index 0000000..09913ce
--- /dev/null
@@ -0,0 +1,117 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_output_buffer.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "gn/err.h"
+#include "gn/file_writer.h"
+#include "gn/filesystem_utils.h"
+
+#include <fstream>
+
+std::string StringOutputBuffer::str() const {
+  std::string result;
+  size_t data_size = size();
+  result.reserve(data_size);
+  for (size_t nn = 0; nn < pages_.size(); ++nn) {
+    size_t wanted_size = std::min(kPageSize, data_size - nn * kPageSize);
+    result.append(pages_[nn]->data(), wanted_size);
+  }
+  return result;
+}
+
+void StringOutputBuffer::Append(const char* str, size_t len) {
+  Append(std::string_view(str, len));
+}
+
+void StringOutputBuffer::Append(std::string_view str) {
+  while (str.size() > 0) {
+    if (page_free_size() == 0) {
+      // Allocate a new page.
+      pages_.push_back(std::make_unique<Page>());
+      pos_ = 0;
+    }
+    size_t size = std::min(page_free_size(), str.size());
+    memcpy(pages_.back()->data() + pos_, str.data(), size);
+    pos_ += size;
+    str.remove_prefix(size);
+  }
+}
+
+void StringOutputBuffer::Append(char c) {
+  if (page_free_size() == 0) {
+    // Allocate a new page.
+    pages_.push_back(std::make_unique<Page>());
+    pos_ = 0;
+  }
+  pages_.back()->data()[pos_] = c;
+  pos_ += 1;
+}
+
+bool StringOutputBuffer::ContentsEqual(const base::FilePath& file_path) const {
+  // Compare file and stream sizes first. Quick and will save us some time if
+  // they are different sizes.
+  size_t data_size = size();
+  int64_t file_size;
+  if (!base::GetFileSize(file_path, &file_size) ||
+      static_cast<size_t>(file_size) != data_size) {
+    return false;
+  }
+
+  // Open the file in binary mode.
+  std::ifstream file(file_path.As8Bit().c_str(), std::ios::binary);
+  if (!file.is_open())
+    return false;
+
+  size_t page_count = pages_.size();
+  Page file_page;
+  for (size_t nn = 0; nn < page_count; ++nn) {
+    size_t wanted_size = std::min(data_size - nn * kPageSize, kPageSize);
+    file.read(file_page.data(), wanted_size);
+    if (!file.good())
+      return false;
+
+    if (memcmp(file_page.data(), pages_[nn]->data(), wanted_size) != 0)
+      return false;
+  }
+  return true;
+}
+
+// Write the contents of this instance to a file at |file_path|.
+bool StringOutputBuffer::WriteToFile(const base::FilePath& file_path,
+                                     Err* err) const {
+  // Create the directory if necessary.
+  if (!base::CreateDirectory(file_path.DirName())) {
+    if (err) {
+      *err =
+          Err(Location(), "Unable to create directory.",
+              "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
+    }
+    return false;
+  }
+
+  size_t data_size = size();
+  size_t page_count = pages_.size();
+
+  FileWriter writer;
+  bool success = writer.Create(file_path);
+  if (success) {
+    for (size_t nn = 0; nn < page_count; ++nn) {
+      size_t wanted_size = std::min(data_size - nn * kPageSize, kPageSize);
+      success = writer.Write(std::string_view(pages_[nn]->data(), wanted_size));
+      if (!success)
+        break;
+    }
+  }
+  if (!writer.Close())
+    success = false;
+
+  if (!success && err) {
+    *err = Err(Location(), "Unable to write file.",
+               "I was writing \"" + FilePathToUTF8(file_path) + "\".");
+  }
+  return success;
+}
diff --git a/src/gn/string_output_buffer.h b/src/gn/string_output_buffer.h
new file mode 100644 (file)
index 0000000..d2a42c9
--- /dev/null
@@ -0,0 +1,94 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_STRING_OUTPUT_BUFFER_H_
+#define TOOLS_GN_STRING_OUTPUT_BUFFER_H_
+
+#include <array>
+#include <memory>
+#include <streambuf>
+#include <string>
+#include <vector>
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+class Err;
+
+// An append-only very large storage area for string data. Useful for the parts
+// of GN that need to generate huge output files (e.g. --ide=json will create
+// a 139 MiB project.json file for the Fuchsia build).
+//
+// Usage is the following:
+//
+//   1) Create instance.
+//
+//   2) Use operator<<, or Append() to append data to the instance.
+//
+//   3) Alternatively, create an std::ostream that takes its address as
+//      argument, then use the output stream as usual to append data to it.
+//
+//      StringOutputBuffer storage;
+//      std::ostream out(&storage);
+//      out << "Hello world!";
+//
+//   4) Use ContentsEqual() to compare the instance's content with that of a
+//      given file.
+//
+//   5) Use WriteToFile() to write the content to a given file.
+//
+class StringOutputBuffer : public std::streambuf {
+ public:
+  StringOutputBuffer() = default;
+
+  // Convert content to single std::string instance. Useful for unit-testing.
+  std::string str() const;
+
+  // Return the number of characters stored in this instance.
+  size_t size() const { return (pages_.size() - 1u) * kPageSize + pos_; }
+
+  // Append string to this instance.
+  void Append(const char* str, size_t len);
+  void Append(std::string_view str);
+  void Append(char c);
+
+  StringOutputBuffer& operator<<(std::string_view str) {
+    Append(str);
+    return *this;
+  }
+
+  // Compare the content of this instance with that of the file at |file_path|.
+  bool ContentsEqual(const base::FilePath& file_path) const;
+
+  // Write the contents of this instance to a file at |file_path|.
+  bool WriteToFile(const base::FilePath& file_path, Err* err) const;
+
+  static size_t GetPageSizeForTesting() { return kPageSize; }
+
+ protected:
+  // Called by std::ostream to write |n| chars from |s|.
+  std::streamsize xsputn(const char* s, std::streamsize n) override {
+    Append(s, static_cast<size_t>(n));
+    return n;
+  }
+
+  // Called by std::ostream to write a single character.
+  int_type overflow(int_type ch) override {
+    Append(static_cast<char>(ch));
+    return 1;
+  }
+
+ private:
+  // Return the number of free bytes in the current page.
+  size_t page_free_size() const { return kPageSize - pos_; }
+
+  static constexpr size_t kPageSize = 65536;
+  using Page = std::array<char, kPageSize>;
+
+  size_t pos_ = kPageSize;
+  std::vector<std::unique_ptr<Page>> pages_;
+};
+
+#endif  // TOOLS_GN_STRING_OUTPUT_BUFFER_H_
diff --git a/src/gn/string_output_buffer_unittest.cc b/src/gn/string_output_buffer_unittest.cc
new file mode 100644 (file)
index 0000000..6dcb741
--- /dev/null
@@ -0,0 +1,134 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_output_buffer.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "util/test/test.h"
+
+namespace {
+
+// Create a test string of |size| characters with pseudo-random ASCII content.
+std::string CreateTestString(size_t size, size_t seed = 0) {
+  std::string result;
+  result.resize(size);
+  for (size_t n = 0; n < size; ++n) {
+    int offset = (size + seed + n * 1337);
+    char ch = ' ' + offset % (127 - 32);
+    result[n] = ch;
+  }
+  return result;
+}
+
+}  // namespace
+
+TEST(StringOutputBuffer, Append) {
+  const size_t data_size = 100000;
+  std::string data = CreateTestString(data_size);
+
+  const size_t num_spans = 50;
+  const size_t span_size = data_size / num_spans;
+
+  StringOutputBuffer buffer;
+
+  for (size_t n = 0; n < num_spans; ++n) {
+    size_t start_offset = n * span_size;
+    size_t end_offset = std::min(start_offset + span_size, data.size());
+    buffer.Append(&data[start_offset], end_offset - start_offset);
+  }
+
+  EXPECT_EQ(data.size(), buffer.size());
+  ASSERT_STREQ(data.c_str(), buffer.str().c_str());
+}
+
+TEST(StringOutputBuffer, AppendWithPageSizeMultiples) {
+  const size_t page_size = StringOutputBuffer::GetPageSizeForTesting();
+  const size_t page_count = 100;
+  const size_t data_size = page_size * page_count;
+  std::string data = CreateTestString(data_size);
+
+  StringOutputBuffer buffer;
+
+  for (size_t n = 0; n < page_count; ++n) {
+    size_t start_offset = n * page_size;
+    buffer.Append(&data[start_offset], page_size);
+  }
+
+  EXPECT_EQ(data.size(), buffer.size());
+  ASSERT_STREQ(data.c_str(), buffer.str().c_str());
+}
+
+TEST(StringOutput, WrappedByStdOstream) {
+  const size_t data_size = 100000;
+  std::string data = CreateTestString(data_size);
+
+  const size_t num_spans = 50;
+  const size_t span_size = data_size / num_spans;
+
+  StringOutputBuffer buffer;
+  std::ostream out(&buffer);
+
+  for (size_t n = 0; n < num_spans; ++n) {
+    size_t start_offset = n * span_size;
+    size_t end_offset = std::min(start_offset + span_size, data.size());
+    out << std::string_view(&data[start_offset], end_offset - start_offset);
+  }
+
+  EXPECT_EQ(data.size(), buffer.size());
+  ASSERT_STREQ(data.c_str(), buffer.str().c_str());
+}
+
+TEST(StringOutputBuffer, ContentsEqual) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  const size_t data_size = 100000;
+  std::string data = CreateTestString(data_size);
+
+  base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
+  base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size()));
+
+  {
+    StringOutputBuffer buffer;
+    buffer.Append(data);
+
+    EXPECT_TRUE(buffer.ContentsEqual(file_path));
+
+    // Different length and contents.
+    buffer << "extra";
+    EXPECT_FALSE(buffer.ContentsEqual(file_path));
+  }
+
+  // The same length, different contents.
+  {
+    StringOutputBuffer buffer;
+    buffer << CreateTestString(data_size, 1);
+
+    EXPECT_FALSE(buffer.ContentsEqual(file_path));
+  }
+}
+
+TEST(StringOutputBuffer, WriteToFile) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  const size_t data_size = 100000;
+  std::string data = CreateTestString(data_size);
+
+  // Write if file doesn't exist. Also create directory.
+  base::FilePath file_path =
+      temp_dir.GetPath().AppendASCII("bar").AppendASCII("foo.txt");
+
+  StringOutputBuffer buffer;
+  buffer.Append(data);
+
+  EXPECT_TRUE(buffer.WriteToFile(file_path, nullptr));
+
+  // Verify file was created and has same content.
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+  ASSERT_TRUE(buffer.ContentsEqual(file_path));
+}
diff --git a/src/gn/string_utils.cc b/src/gn/string_utils.cc
new file mode 100644 (file)
index 0000000..b785e75
--- /dev/null
@@ -0,0 +1,356 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_utils.h"
+
+#include <stddef.h>
+#include <cctype>
+
+#include "base/strings/string_number_conversions.h"
+#include "gn/err.h"
+#include "gn/input_file.h"
+#include "gn/parser.h"
+#include "gn/scope.h"
+#include "gn/token.h"
+#include "gn/tokenizer.h"
+#include "gn/value.h"
+
+namespace {
+
+// Constructs an Err indicating a range inside a string. We assume that the
+// token has quotes around it that are not counted by the offset.
+Err ErrInsideStringToken(const Token& token,
+                         size_t offset,
+                         size_t size,
+                         const std::string& msg,
+                         const std::string& help = std::string()) {
+  // The "+1" is skipping over the " at the beginning of the token.
+  int int_offset = static_cast<int>(offset);
+  Location begin_loc(token.location().file(), token.location().line_number(),
+                     token.location().column_number() + int_offset + 1,
+                     token.location().byte() + int_offset + 1);
+  Location end_loc(
+      token.location().file(), token.location().line_number(),
+      token.location().column_number() + int_offset + 1 +
+          static_cast<int>(size),
+      token.location().byte() + int_offset + 1 + static_cast<int>(size));
+  return Err(LocationRange(begin_loc, end_loc), msg, help);
+}
+
+// Notes about expression interpolation. This is based loosly on Dart but is
+// slightly less flexible. In Dart, seeing the ${ in a string is something
+// the toplevel parser knows about, and it will recurse into the block
+// treating it as a first-class {...} block. So even things like this work:
+//   "hello ${"foo}"*2+"bar"}"  =>  "hello foo}foo}bar"
+// (you can see it did not get confused by the nested strings or the nested "}"
+// inside the block).
+//
+// This is cool but complicates the parser for almost no benefit for this
+// non-general-purpose programming language. The main reason expressions are
+// supported here at all are to support "${scope.variable}" and "${list[0]}",
+// neither of which have any of these edge-cases.
+//
+// In this simplified approach, we search for the terminating '}' and execute
+// the result. This means we can't support any expressions with embedded '}'
+// or '"'. To keep people from getting confusing about what's supported and
+// what's not, only identifier and accessor expressions are allowed (neither
+// of these run into any of these edge-cases).
+bool AppendInterpolatedExpression(Scope* scope,
+                                  const Token& token,
+                                  const char* input,
+                                  size_t begin_offset,
+                                  size_t end_offset,
+                                  std::string* output,
+                                  Err* err) {
+  SourceFile empty_source_file;  // Prevent most vexing parse.
+  InputFile input_file(empty_source_file);
+  input_file.SetContents(
+      std::string(&input[begin_offset], end_offset - begin_offset));
+
+  // Tokenize.
+  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err);
+  if (err->has_error()) {
+    // The error will point into our temporary buffer, rewrite it to refer
+    // to the original token. This will make the location information less
+    // precise, but generally there won't be complicated things in string
+    // interpolations.
+    *err = ErrInsideStringToken(token, begin_offset, end_offset - begin_offset,
+                                err->message(), err->help_text());
+    return false;
+  }
+
+  // Parse.
+  std::unique_ptr<ParseNode> node = Parser::ParseExpression(tokens, err);
+  if (err->has_error()) {
+    // Rewrite error as above.
+    *err = ErrInsideStringToken(token, begin_offset, end_offset - begin_offset,
+                                err->message(), err->help_text());
+    return false;
+  }
+  if (!(node->AsIdentifier() || node->AsAccessor())) {
+    *err = ErrInsideStringToken(
+        token, begin_offset, end_offset - begin_offset,
+        "Invalid string interpolation.",
+        "The thing inside the ${} must be an identifier ${foo},\n"
+        "a scope access ${foo.bar}, or a list access ${foo[0]}.");
+    return false;
+  }
+
+  // Evaluate.
+  Value result = node->Execute(scope, err);
+  if (err->has_error()) {
+    // Rewrite error as above.
+    *err = ErrInsideStringToken(token, begin_offset, end_offset - begin_offset,
+                                err->message(), err->help_text());
+    return false;
+  }
+
+  output->append(result.ToString(false));
+  return true;
+}
+
+bool AppendInterpolatedIdentifier(Scope* scope,
+                                  const Token& token,
+                                  const char* input,
+                                  size_t begin_offset,
+                                  size_t end_offset,
+                                  std::string* output,
+                                  Err* err) {
+  std::string_view identifier(&input[begin_offset], end_offset - begin_offset);
+  const Value* value = scope->GetValue(identifier, true);
+  if (!value) {
+    // We assume the input points inside the token.
+    *err = ErrInsideStringToken(
+        token, identifier.data() - token.value().data() - 1, identifier.size(),
+        "Undefined identifier in string expansion.",
+        std::string("\"") + identifier + "\" is not currently in scope.");
+    return false;
+  }
+
+  output->append(value->ToString(false));
+  return true;
+}
+
+// Handles string interpolations: $identifier and ${expression}
+//
+// |*i| is the index into |input| after the $. This will be updated to point to
+// the last character consumed on success. The token is the original string
+// to blame on failure.
+//
+// On failure, returns false and sets the error. On success, appends the
+// result of the interpolation to |*output|.
+bool AppendStringInterpolation(Scope* scope,
+                               const Token& token,
+                               const char* input,
+                               size_t size,
+                               size_t* i,
+                               std::string* output,
+                               Err* err) {
+  size_t dollars_index = *i - 1;
+
+  if (input[*i] == '{') {
+    // Bracketed expression.
+    (*i)++;
+    size_t begin_offset = *i;
+
+    // Find the closing } and check for non-identifier chars. Don't need to
+    // bother checking for the more-restricted first character of an identifier
+    // since the {} unambiguously denotes the range, and identifiers with
+    // invalid names just won't be found later.
+    bool has_non_ident_chars = false;
+    while (*i < size && input[*i] != '}') {
+      has_non_ident_chars |= Tokenizer::IsIdentifierContinuingChar(input[*i]);
+      (*i)++;
+    }
+    if (*i == size) {
+      *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index,
+                                  "Unterminated ${...");
+      return false;
+    }
+
+    // In the common case, the thing inside the {} will actually be a
+    // simple identifier. Avoid all the complicated parsing of accessors
+    // in this case.
+    if (!has_non_ident_chars) {
+      return AppendInterpolatedIdentifier(scope, token, input, begin_offset, *i,
+                                          output, err);
+    }
+    return AppendInterpolatedExpression(scope, token, input, begin_offset, *i,
+                                        output, err);
+  }
+
+  // Simple identifier.
+  // The first char of an identifier is more restricted.
+  if (!Tokenizer::IsIdentifierFirstChar(input[*i])) {
+    *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index + 1,
+                                "$ not followed by an identifier char.",
+                                "It you want a literal $ use \"\\$\".");
+    return false;
+  }
+  size_t begin_offset = *i;
+  (*i)++;
+
+  // Find the first non-identifier char following the string.
+  while (*i < size && Tokenizer::IsIdentifierContinuingChar(input[*i]))
+    (*i)++;
+  size_t end_offset = *i;
+  (*i)--;  // Back up to mark the last character consumed.
+  return AppendInterpolatedIdentifier(scope, token, input, begin_offset,
+                                      end_offset, output, err);
+}
+
+// Handles a hex literal: $0xFF
+//
+// |*i| is the index into |input| after the $. This will be updated to point to
+// the last character consumed on success. The token is the original string
+// to blame on failure.
+//
+// On failure, returns false and sets the error. On success, appends the
+// char with the given hex value to |*output|.
+bool AppendHexByte(Scope* scope,
+                   const Token& token,
+                   const char* input,
+                   size_t size,
+                   size_t* i,
+                   std::string* output,
+                   Err* err) {
+  size_t dollars_index = *i - 1;
+  // "$0" is already known to exist.
+  if (*i + 3 >= size || input[*i + 1] != 'x' || !std::isxdigit(input[*i + 2]) ||
+      !std::isxdigit(input[*i + 3])) {
+    *err = ErrInsideStringToken(
+        token, dollars_index, *i - dollars_index + 1,
+        "Invalid hex character. Hex values must look like 0xFF.");
+    return false;
+  }
+  int value = 0;
+  if (!base::HexStringToInt(std::string_view(&input[*i + 2], 2), &value)) {
+    *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index + 1,
+                                "Could not convert hex value.");
+    return false;
+  }
+  *i += 3;
+  output->push_back(value);
+  return true;
+}
+
+}  // namespace
+
+bool ExpandStringLiteral(Scope* scope,
+                         const Token& literal,
+                         Value* result,
+                         Err* err) {
+  DCHECK(literal.type() == Token::STRING);
+  DCHECK(literal.value().size() > 1);       // Should include quotes.
+  DCHECK(result->type() == Value::STRING);  // Should be already set.
+
+  // The token includes the surrounding quotes, so strip those off.
+  const char* input = &literal.value().data()[1];
+  size_t size = literal.value().size() - 2;
+
+  std::string& output = result->string_value();
+  output.reserve(size);
+  for (size_t i = 0; i < size; i++) {
+    if (input[i] == '\\') {
+      if (i < size - 1) {
+        switch (input[i + 1]) {
+          case '\\':
+          case '"':
+          case '$':
+            output.push_back(input[i + 1]);
+            i++;
+            continue;
+          default:  // Everything else has no meaning: pass the literal.
+            break;
+        }
+      }
+      output.push_back(input[i]);
+    } else if (input[i] == '$') {
+      i++;
+      if (i == size) {
+        *err = ErrInsideStringToken(
+            literal, i - 1, 1, "$ at end of string.",
+            "I was expecting an identifier, 0xFF, or {...} after the $.");
+        return false;
+      }
+      if (input[i] == '0') {
+        if (!AppendHexByte(scope, literal, input, size, &i, &output, err))
+          return false;
+      } else if (!AppendStringInterpolation(scope, literal, input, size, &i,
+                                            &output, err))
+        return false;
+    } else {
+      output.push_back(input[i]);
+    }
+  }
+  return true;
+}
+
+size_t EditDistance(const std::string_view& s1,
+                    const std::string_view& s2,
+                    size_t max_edit_distance) {
+  // The algorithm implemented below is the "classic"
+  // dynamic-programming algorithm for computing the Levenshtein
+  // distance, which is described here:
+  //
+  //   http://en.wikipedia.org/wiki/Levenshtein_distance
+  //
+  // Although the algorithm is typically described using an m x n
+  // array, only one row plus one element are used at a time, so this
+  // implementation just keeps one vector for the row.  To update one entry,
+  // only the entries to the left, top, and top-left are needed.  The left
+  // entry is in row[x-1], the top entry is what's in row[x] from the last
+  // iteration, and the top-left entry is stored in previous.
+  size_t m = s1.size();
+  size_t n = s2.size();
+
+  std::vector<size_t> row(n + 1);
+  for (size_t i = 1; i <= n; ++i)
+    row[i] = i;
+
+  for (size_t y = 1; y <= m; ++y) {
+    row[0] = y;
+    size_t best_this_row = row[0];
+
+    size_t previous = y - 1;
+    for (size_t x = 1; x <= n; ++x) {
+      size_t old_row = row[x];
+      row[x] = std::min(previous + (s1[y - 1] == s2[x - 1] ? 0u : 1u),
+                        std::min(row[x - 1], row[x]) + 1u);
+      previous = old_row;
+      best_this_row = std::min(best_this_row, row[x]);
+    }
+
+    if (max_edit_distance && best_this_row > max_edit_distance)
+      return max_edit_distance + 1;
+  }
+
+  return row[n];
+}
+
+std::string_view SpellcheckString(const std::string_view& text,
+                                  const std::vector<std::string_view>& words) {
+  const size_t kMaxValidEditDistance = 3u;
+
+  size_t min_distance = kMaxValidEditDistance + 1u;
+  std::string_view result;
+  for (std::string_view word : words) {
+    size_t distance = EditDistance(word, text, kMaxValidEditDistance);
+    if (distance < min_distance) {
+      min_distance = distance;
+      result = word;
+    }
+  }
+  return result;
+}
+
+std::string ReadStdin() {
+  char buffer[4 << 10];
+  std::string result;
+  size_t len;
+  while ((len = fread(buffer, 1, sizeof(buffer), stdin)) > 0)
+    result.append(buffer, len);
+  // TODO(thakis): Check ferror(stdin)?
+  return result;
+}
diff --git a/src/gn/string_utils.h b/src/gn/string_utils.h
new file mode 100644 (file)
index 0000000..a98a485
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_STRING_UTILS_H_
+#define TOOLS_GN_STRING_UTILS_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+class Err;
+class Scope;
+class Token;
+class Value;
+
+inline std::string operator+(const std::string& a, const std::string_view& b) {
+  std::string ret;
+  ret.reserve(a.size() + b.size());
+  ret.assign(a);
+  ret.append(b.data(), b.size());
+  return ret;
+}
+
+inline std::string operator+(const std::string_view& a, const std::string& b) {
+  std::string ret;
+  ret.reserve(a.size() + b.size());
+  ret.assign(a.data(), a.size());
+  ret.append(b);
+  return ret;
+}
+
+// Unescapes and expands variables in the given literal, writing the result
+// to the given value. On error, sets |err| and returns false.
+bool ExpandStringLiteral(Scope* scope,
+                         const Token& literal,
+                         Value* result,
+                         Err* err);
+
+// Returns the minimum number of inserts, deleted, and replacements of
+// characters needed to transform s1 to s2, or max_edit_distance + 1 if
+// transforming s1 into s2 isn't possible in at most max_edit_distance steps.
+size_t EditDistance(const std::string_view& s1,
+                    const std::string_view& s2,
+                    size_t max_edit_distance);
+
+// Given a string |text| and a vector of correctly-spelled strings |words|,
+// returns the first string in |words| closest to |text|, or an empty
+// std::string_view if none of the strings in |words| is close.
+std::string_view SpellcheckString(const std::string_view& text,
+                                  const std::vector<std::string_view>& words);
+
+// Reads stdin until end-of-data and returns what it read.
+std::string ReadStdin();
+
+#endif  // TOOLS_GN_STRING_UTILS_H_
diff --git a/src/gn/string_utils_unittest.cc b/src/gn/string_utils_unittest.cc
new file mode 100644 (file)
index 0000000..ac412d9
--- /dev/null
@@ -0,0 +1,159 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/string_utils.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "gn/err.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/token.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+namespace {
+
+bool CheckExpansionCase(const char* input, const char* expected, bool success) {
+  Scope scope(static_cast<const Settings*>(nullptr));
+  int64_t one = 1;
+  scope.SetValue("one", Value(nullptr, one), nullptr);
+  scope.SetValue("onestring", Value(nullptr, "one"), nullptr);
+
+  // Nested scope called "onescope" with a value "one" inside it.
+  std::unique_ptr<Scope> onescope =
+      std::make_unique<Scope>(static_cast<const Settings*>(nullptr));
+  onescope->SetValue("one", Value(nullptr, one), nullptr);
+  scope.SetValue("onescope", Value(nullptr, std::move(onescope)), nullptr);
+
+  // List called "onelist" with one value that maps to 1.
+  Value onelist(nullptr, Value::LIST);
+  onelist.list_value().push_back(Value(nullptr, one));
+  scope.SetValue("onelist", onelist, nullptr);
+
+  // Construct the string token, which includes the quotes.
+  std::string literal_string;
+  literal_string.push_back('"');
+  literal_string.append(input);
+  literal_string.push_back('"');
+  Token literal(Location(), Token::STRING, literal_string);
+
+  Value result(nullptr, Value::STRING);
+  Err err;
+  bool ret = ExpandStringLiteral(&scope, literal, &result, &err);
+
+  // Err and return value should agree.
+  EXPECT_NE(ret, err.has_error());
+
+  if (ret != success)
+    return false;
+
+  if (!success)
+    return true;  // Don't check result on failure.
+  return result.string_value() == expected;
+}
+
+}  // namespace
+
+TEST(StringUtils, ExpandStringLiteralIdentifier) {
+  EXPECT_TRUE(CheckExpansionCase("", "", true));
+  EXPECT_TRUE(CheckExpansionCase("hello", "hello", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #$one", "hello #1", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #$one/two", "hello #1/two", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #${one}", "hello #1", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #${one}one", "hello #1one", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #${one}$one", "hello #11", true));
+  EXPECT_TRUE(CheckExpansionCase("$onestring${one}$one", "one11", true));
+  EXPECT_TRUE(CheckExpansionCase("$onescope", "{\n  one = 1\n}", true));
+  EXPECT_TRUE(CheckExpansionCase("$onelist", "[1]", true));
+
+  // Hex values
+  EXPECT_TRUE(CheckExpansionCase("$0x0AA",
+                                 "\x0A"
+                                 "A",
+                                 true));
+  EXPECT_TRUE(CheckExpansionCase("$0x0a$0xfF", "\x0A\xFF", true));
+
+  // Errors
+  EXPECT_TRUE(CheckExpansionCase("hello #$", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hello #$%", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hello #${", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hello #${}", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hello #$nonexistent", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hello #${unterminated", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex truncated: $0", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex truncated: $0x", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex truncated: $0x0", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0a", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0x1z", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0xz1", nullptr, false));
+
+  // Unknown backslash values aren't special.
+  EXPECT_TRUE(CheckExpansionCase("\\", "\\", true));
+  EXPECT_TRUE(CheckExpansionCase("\\b", "\\b", true));
+
+  // Backslashes escape some special things. \"\$\\ -> "$\  Note that gtest
+  // doesn't like this escape sequence so we have to put it out-of-line.
+  const char* in = "\\\"\\$\\\\";
+  const char* out = "\"$\\";
+  EXPECT_TRUE(CheckExpansionCase(in, out, true));
+}
+
+TEST(StringUtils, ExpandStringLiteralExpression) {
+  // Accessing the scope.
+  EXPECT_TRUE(CheckExpansionCase("hello #${onescope.one}", "hello #1", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #${onescope.two}", nullptr, false));
+
+  // Accessing the list.
+  EXPECT_TRUE(CheckExpansionCase("hello #${onelist[0]}", "hello #1", true));
+  EXPECT_TRUE(CheckExpansionCase("hello #${onelist[1]}", nullptr, false));
+
+  // Trying some other (otherwise valid) expressions should fail.
+  EXPECT_TRUE(CheckExpansionCase("${1 + 2}", nullptr, false));
+  EXPECT_TRUE(CheckExpansionCase("${print(1)}", nullptr, false));
+}
+
+TEST(StringUtils, EditDistance) {
+  EXPECT_EQ(3u, EditDistance("doom melon", "dune melon", 100));
+  EXPECT_EQ(2u, EditDistance("doom melon", "dune melon", 1));
+
+  EXPECT_EQ(2u, EditDistance("ab", "ba", 100));
+  EXPECT_EQ(2u, EditDistance("ba", "ab", 100));
+
+  EXPECT_EQ(2u, EditDistance("ananas", "banana", 100));
+  EXPECT_EQ(2u, EditDistance("banana", "ananas", 100));
+
+  EXPECT_EQ(2u, EditDistance("unclear", "nuclear", 100));
+  EXPECT_EQ(2u, EditDistance("nuclear", "unclear", 100));
+
+  EXPECT_EQ(3u, EditDistance("chrome", "chromium", 100));
+  EXPECT_EQ(3u, EditDistance("chromium", "chrome", 100));
+
+  EXPECT_EQ(4u, EditDistance("", "abcd", 100));
+  EXPECT_EQ(4u, EditDistance("abcd", "", 100));
+
+  EXPECT_EQ(4u, EditDistance("xxx", "xxxxxxx", 100));
+  EXPECT_EQ(4u, EditDistance("xxxxxxx", "xxx", 100));
+
+  EXPECT_EQ(7u, EditDistance("yyy", "xxxxxxx", 100));
+  EXPECT_EQ(7u, EditDistance("xxxxxxx", "yyy", 100));
+}
+
+TEST(StringUtils, SpellcheckString) {
+  std::vector<std::string_view> words;
+  words.push_back("your");
+  words.push_back("bravado");
+  words.push_back("won\'t");
+  words.push_back("help");
+  words.push_back("you");
+  words.push_back("now");
+
+  EXPECT_EQ("help", SpellcheckString("halp", words));
+
+  // barbados has an edit distance of 4 from bravado, so there's no suggestion.
+  EXPECT_TRUE(SpellcheckString("barbados", words).empty());
+}
diff --git a/src/gn/substitution_list.cc b/src/gn/substitution_list.cc
new file mode 100644 (file)
index 0000000..ecef4f5
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/substitution_list.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include "gn/value.h"
+
+SubstitutionList::SubstitutionList() = default;
+
+SubstitutionList::SubstitutionList(const SubstitutionList& other) = default;
+
+SubstitutionList::~SubstitutionList() = default;
+
+bool SubstitutionList::Parse(const Value& value, Err* err) {
+  if (!value.VerifyTypeIs(Value::LIST, err))
+    return false;
+
+  const std::vector<Value>& input_list = value.list_value();
+  list_.resize(input_list.size());
+  for (size_t i = 0; i < input_list.size(); i++) {
+    if (!list_[i].Parse(input_list[i], err))
+      return false;
+  }
+
+  SubstitutionBits bits;
+  FillRequiredTypes(&bits);
+  bits.FillVector(&required_types_);
+  return true;
+}
+
+bool SubstitutionList::Parse(const std::vector<std::string>& values,
+                             const ParseNode* origin,
+                             Err* err) {
+  list_.resize(values.size());
+  for (size_t i = 0; i < values.size(); i++) {
+    if (!list_[i].Parse(values[i], origin, err))
+      return false;
+  }
+
+  SubstitutionBits bits;
+  FillRequiredTypes(&bits);
+  bits.FillVector(&required_types_);
+  return true;
+}
+
+SubstitutionList SubstitutionList::MakeForTest(const char* a,
+                                               const char* b,
+                                               const char* c) {
+  std::vector<std::string> input_strings;
+  input_strings.push_back(a);
+  if (b)
+    input_strings.push_back(b);
+  if (c)
+    input_strings.push_back(c);
+
+  Err err;
+  SubstitutionList result;
+  result.Parse(input_strings, nullptr, &err);
+  return result;
+}
+
+void SubstitutionList::FillRequiredTypes(SubstitutionBits* bits) const {
+  for (const auto& item : list_)
+    item.FillRequiredTypes(bits);
+}
diff --git a/src/gn/substitution_list.h b/src/gn/substitution_list.h
new file mode 100644 (file)
index 0000000..c1985bb
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SUBSTITUTION_LIST_H_
+#define TOOLS_GN_SUBSTITUTION_LIST_H_
+
+#include <string>
+#include <vector>
+
+#include "gn/substitution_pattern.h"
+
+// Represents a list of strings with {{substitution_patterns}} in them.
+class SubstitutionList {
+ public:
+  SubstitutionList();
+  SubstitutionList(const SubstitutionList& other);
+  ~SubstitutionList();
+
+  bool Parse(const Value& value, Err* err);
+  bool Parse(const std::vector<std::string>& values,
+             const ParseNode* origin,
+             Err* err);
+
+  // Makes a SubstitutionList from the given hardcoded patterns.
+  static SubstitutionList MakeForTest(const char* a,
+                                      const char* b = nullptr,
+                                      const char* c = nullptr);
+
+  const std::vector<SubstitutionPattern>& list() const { return list_; }
+
+  // Returns a list of all substitution types used by the patterns in this
+  // list, with the exception of LITERAL.
+  const std::vector<const Substitution*>& required_types() const {
+    return required_types_;
+  }
+
+  void FillRequiredTypes(SubstitutionBits* bits) const;
+
+ private:
+  std::vector<SubstitutionPattern> list_;
+
+  std::vector<const Substitution*> required_types_;
+};
+
+#endif  // TOOLS_GN_SUBSTITUTION_LIST_H_
diff --git a/src/gn/substitution_pattern.cc b/src/gn/substitution_pattern.cc
new file mode 100644 (file)
index 0000000..8465903
--- /dev/null
@@ -0,0 +1,144 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/substitution_pattern.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/value.h"
+
+SubstitutionPattern::Subrange::Subrange() : type(&SubstitutionLiteral) {}
+
+SubstitutionPattern::Subrange::Subrange(const Substitution* t,
+                                        const std::string& l)
+    : type(t), literal(l) {}
+
+SubstitutionPattern::Subrange::~Subrange() = default;
+
+SubstitutionPattern::SubstitutionPattern() : origin_(nullptr) {}
+
+SubstitutionPattern::SubstitutionPattern(const SubstitutionPattern& other) =
+    default;
+
+SubstitutionPattern::~SubstitutionPattern() = default;
+
+bool SubstitutionPattern::Parse(const Value& value, Err* err) {
+  if (!value.VerifyTypeIs(Value::STRING, err))
+    return false;
+  return Parse(value.string_value(), value.origin(), err);
+}
+
+bool SubstitutionPattern::Parse(const std::string& str,
+                                const ParseNode* origin,
+                                Err* err) {
+  DCHECK(ranges_.empty());  // Should only be called once.
+
+  size_t cur = 0;
+  while (true) {
+    size_t next = str.find("{{", cur);
+
+    // Pick up everything from the previous spot to here as a literal.
+    if (next == std::string::npos) {
+      if (cur != str.size())
+        ranges_.push_back(Subrange(&SubstitutionLiteral, str.substr(cur)));
+      break;
+    } else if (next > cur) {
+      ranges_.push_back(
+          Subrange(&SubstitutionLiteral, str.substr(cur, next - cur)));
+    }
+
+    // Find which specific pattern this corresponds to.
+    bool found_match = false;
+    for (const SubstitutionTypes* types : AllSubstitutions) {
+      for (const Substitution* sub : *types) {
+        const char* cur_pattern = sub->name;
+        size_t cur_len = strlen(cur_pattern);
+        if (str.compare(next, cur_len, cur_pattern) == 0) {
+          ranges_.push_back(Subrange(sub));
+          cur = next + cur_len;
+          found_match = true;
+          break;
+        }
+      }
+    }
+
+    // Expect all occurrences of {{ to resolve to a pattern.
+    if (!found_match) {
+      // Could make this error message more friendly if it comes up a lot. But
+      // most people will not be writing substitution patterns and the code
+      // to exactly indicate the error location is tricky.
+      *err = Err(origin, "Unknown substitution pattern",
+                 "Found a {{ at offset " + base::NumberToString(next) +
+                     " and did not find a known substitution following it.");
+      ranges_.clear();
+      return false;
+    }
+  }
+
+  origin_ = origin;
+
+  // Fill required types vector.
+  SubstitutionBits bits;
+  FillRequiredTypes(&bits);
+  bits.FillVector(&required_types_);
+  return true;
+}
+
+// static
+SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) {
+  Err err;
+  SubstitutionPattern pattern;
+  CHECK(pattern.Parse(str, nullptr, &err)) << err.message();
+  return pattern;
+}
+
+std::string SubstitutionPattern::AsString() const {
+  std::string result;
+  for (const auto& elem : ranges_) {
+    if (elem.type == &SubstitutionLiteral)
+      result.append(elem.literal);
+    else
+      result.append(elem.type->name);
+  }
+  return result;
+}
+
+void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
+  for (const auto& elem : ranges_) {
+    if (elem.type != &SubstitutionLiteral)
+      bits->used.insert(elem.type);
+  }
+}
+
+bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
+                                        Err* err) const {
+  if (ranges_.empty()) {
+    *err = Err(origin_, "This is empty but I was expecting an output file.");
+    return false;
+  }
+
+  if (ranges_[0].type == &SubstitutionLiteral) {
+    // If the first thing is a literal, it must start with the output dir.
+    if (!EnsureStringIsInOutputDir(build_settings->build_dir(),
+                                   ranges_[0].literal, origin_, err))
+      return false;
+  } else {
+    // Otherwise, the first subrange must be a pattern that expands to
+    // something in the output directory.
+    if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
+      *err =
+          Err(origin_, "File is not inside output directory.",
+              "The given file should be in the output directory. Normally you\n"
+              "would specify\n\"$target_out_dir/foo\" or "
+              "\"{{source_gen_dir}}/foo\".");
+      return false;
+    }
+  }
+
+  return true;
+}
diff --git a/src/gn/substitution_pattern.h b/src/gn/substitution_pattern.h
new file mode 100644 (file)
index 0000000..f6ad22e
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SUBSTITUTION_PATTERN_H_
+#define TOOLS_GN_SUBSTITUTION_PATTERN_H_
+
+#include <string>
+#include <vector>
+
+#include "gn/substitution_type.h"
+
+class BuildSettings;
+class Err;
+class ParseNode;
+class Value;
+
+// Represents a string with {{substitution_patterns}} in them.
+class SubstitutionPattern {
+ public:
+  struct Subrange {
+    Subrange();
+    explicit Subrange(const Substitution* t, const std::string& l = std::string());
+    ~Subrange();
+
+    inline bool operator==(const Subrange& other) const {
+      return type == other.type && literal == other.literal;
+    }
+
+    const Substitution* type;
+
+    // When type_ == LITERAL, this specifies the literal.
+    std::string literal;
+  };
+
+  SubstitutionPattern();
+  SubstitutionPattern(const SubstitutionPattern& other);
+  ~SubstitutionPattern();
+
+  // Parses the given string and fills in the pattern. The pattern must only
+  // be initialized once. On failure, returns false and sets the error.
+  bool Parse(const Value& value, Err* err);
+  bool Parse(const std::string& str, const ParseNode* origin, Err* err);
+
+  // Makes a pattern given a hardcoded string. Will assert if the string is
+  // not a valid pattern.
+  static SubstitutionPattern MakeForTest(const char* str);
+
+  // Returns the pattern as a string with substitutions in them.
+  std::string AsString() const;
+
+  // Sets the bits in the given vector corresponding to the substitutions used
+  // by this pattern. SubstitutionLiteral is ignored.
+  void FillRequiredTypes(SubstitutionBits* bits) const;
+
+  // Checks whether this pattern resolves to something in the output directory
+  // for the given build settings. If not, returns false and fills in the given
+  // error.
+  bool IsInOutputDir(const BuildSettings* build_settings, Err* err) const;
+
+  // Returns a vector listing the substitutions used by this pattern, not
+  // counting SubstitutionLiteral.
+  const std::vector<const Substitution*>& required_types() const {
+    return required_types_;
+  }
+
+  const std::vector<Subrange>& ranges() const { return ranges_; }
+  bool empty() const { return ranges_.empty(); }
+
+  const ParseNode* origin() const { return origin_; }
+
+ private:
+  std::vector<Subrange> ranges_;
+  const ParseNode* origin_;
+
+  std::vector<const Substitution*> required_types_;
+};
+
+#endif  // TOOLS_GN_SUBSTITUTION_PATTERN_H_
diff --git a/src/gn/substitution_pattern_unittest.cc b/src/gn/substitution_pattern_unittest.cc
new file mode 100644 (file)
index 0000000..76e4dec
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/substitution_pattern.h"
+
+#include "gn/err.h"
+#include "gn/rust_substitution_type.h"
+#include "util/test/test.h"
+
+TEST(SubstitutionPattern, ParseLiteral) {
+  SubstitutionPattern pattern;
+  Err err;
+  EXPECT_TRUE(pattern.Parse("This is a literal", nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(1u, pattern.ranges().size());
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[0].type);
+  EXPECT_EQ("This is a literal", pattern.ranges()[0].literal);
+}
+
+TEST(SubstitutionPattern, ParseComplex) {
+  SubstitutionPattern pattern;
+  Err err;
+  EXPECT_TRUE(pattern.Parse(
+      "AA{{source}}{{source_name_part}}BB{{source_file_part}}", nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(5u, pattern.ranges().size());
+
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[0].type);
+  EXPECT_EQ("AA", pattern.ranges()[0].literal);
+  EXPECT_EQ(&SubstitutionSource, pattern.ranges()[1].type);
+  EXPECT_EQ(&SubstitutionSourceNamePart, pattern.ranges()[2].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[3].type);
+  EXPECT_EQ("BB", pattern.ranges()[3].literal);
+  EXPECT_EQ(&SubstitutionSourceFilePart, pattern.ranges()[4].type);
+}
+
+TEST(SubstitutionPattern, ParseErrors) {
+  SubstitutionPattern pattern;
+  Err err;
+  EXPECT_FALSE(pattern.Parse("AA{{source", nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+
+  err = Err();
+  EXPECT_FALSE(pattern.Parse("{{source_of_evil}}", nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+
+  err = Err();
+  EXPECT_FALSE(pattern.Parse("{{source{{source}}", nullptr, &err));
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(SubstitutionPattern, ParseRust) {
+  SubstitutionPattern pattern;
+  Err err;
+  EXPECT_TRUE(pattern.Parse(
+      "AA{{rustflags}}{{rustenv}}BB{{crate_name}}{{rustdeps}}CC{{externs}}",
+      nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(8u, pattern.ranges().size());
+
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[0].type);
+  EXPECT_EQ("AA", pattern.ranges()[0].literal);
+  EXPECT_EQ(&kRustSubstitutionRustFlags, pattern.ranges()[1].type);
+  EXPECT_EQ(&kRustSubstitutionRustEnv, pattern.ranges()[2].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[3].type);
+  EXPECT_EQ("BB", pattern.ranges()[3].literal);
+  EXPECT_EQ(&kRustSubstitutionCrateName, pattern.ranges()[4].type);
+  EXPECT_EQ(&kRustSubstitutionRustDeps, pattern.ranges()[5].type);
+  EXPECT_EQ(&SubstitutionLiteral, pattern.ranges()[6].type);
+  EXPECT_EQ("CC", pattern.ranges()[6].literal);
+  EXPECT_EQ(&kRustSubstitutionExterns, pattern.ranges()[7].type);
+}
\ No newline at end of file
diff --git a/src/gn/substitution_type.cc b/src/gn/substitution_type.cc
new file mode 100644 (file)
index 0000000..06c21b3
--- /dev/null
@@ -0,0 +1,204 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/substitution_type.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "gn/c_substitution_type.h"
+#include "gn/err.h"
+#include "gn/rust_substitution_type.h"
+
+const std::vector<SubstitutionTypes*> AllSubstitutions = {
+    &GeneralSubstitutions, &CSubstitutions, &RustSubstitutions};
+
+const SubstitutionTypes GeneralSubstitutions = {
+    &SubstitutionLiteral,
+
+    &SubstitutionOutput,
+    &SubstitutionLabel,
+    &SubstitutionLabelName,
+    &SubstitutionLabelNoToolchain,
+    &SubstitutionRootGenDir,
+    &SubstitutionRootOutDir,
+    &SubstitutionOutputDir,
+    &SubstitutionOutputExtension,
+    &SubstitutionTargetGenDir,
+    &SubstitutionTargetOutDir,
+    &SubstitutionTargetOutputName,
+
+    &SubstitutionSource,
+    &SubstitutionSourceNamePart,
+    &SubstitutionSourceFilePart,
+    &SubstitutionSourceDir,
+    &SubstitutionSourceRootRelativeDir,
+    &SubstitutionSourceGenDir,
+    &SubstitutionSourceOutDir,
+    &SubstitutionSourceTargetRelative,
+
+    &SubstitutionBundleRootDir,
+    &SubstitutionBundleContentsDir,
+    &SubstitutionBundleResourcesDir,
+    &SubstitutionBundleExecutableDir,
+
+    &SubstitutionBundleProductType,
+    &SubstitutionBundlePartialInfoPlist,
+    &SubstitutionXcassetsCompilerFlags,
+
+    &SubstitutionRspFileName,
+};
+
+const Substitution SubstitutionLiteral = {"<<literal>>", nullptr};
+
+const Substitution SubstitutionSource = {"{{source}}", "in"};
+const Substitution SubstitutionOutput = {"{{output}}", "out"};
+
+const Substitution SubstitutionSourceNamePart = {"{{source_name_part}}",
+                                                 "source_name_part"};
+const Substitution SubstitutionSourceFilePart = {"{{source_file_part}}",
+                                                 "source_file_part"};
+const Substitution SubstitutionSourceDir = {"{{source_dir}}", "source_dir"};
+const Substitution SubstitutionSourceRootRelativeDir = {
+    "{{source_root_relative_dir}}", "source_root_relative_dir"};
+const Substitution SubstitutionSourceGenDir = {"{{source_gen_dir}}",
+                                               "source_gen_dir"};
+const Substitution SubstitutionSourceOutDir = {"{{source_out_dir}}",
+                                               "source_out_dir"};
+const Substitution SubstitutionSourceTargetRelative = {
+    "{{source_target_relative}}", "source_target_relative"};
+
+// Valid for all compiler and linker tools. These depend on the target and
+// do not vary on a per-file basis.
+const Substitution SubstitutionLabel = {"{{label}}", "label"};
+const Substitution SubstitutionLabelName = {"{{label_name}}", "label_name"};
+const Substitution SubstitutionLabelNoToolchain = {"{{label_no_toolchain}}",
+                                                   "label_no_toolchain"};
+const Substitution SubstitutionRootGenDir = {"{{root_gen_dir}}",
+                                             "root_gen_dir"};
+const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
+                                             "root_out_dir"};
+const Substitution SubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
+const Substitution SubstitutionOutputExtension = {"{{output_extension}}",
+                                                  "output_extension"};
+const Substitution SubstitutionTargetGenDir = {"{{target_gen_dir}}",
+                                               "target_gen_dir"};
+const Substitution SubstitutionTargetOutDir = {"{{target_out_dir}}",
+                                               "target_out_dir"};
+const Substitution SubstitutionTargetOutputName = {"{{target_output_name}}",
+                                                   "target_output_name"};
+
+// Valid for bundle_data targets.
+const Substitution SubstitutionBundleRootDir = {"{{bundle_root_dir}}",
+                                                "bundle_root_dir"};
+const Substitution SubstitutionBundleContentsDir = {"{{bundle_contents_dir}}",
+                                                    "bundle_contents_dir"};
+const Substitution SubstitutionBundleResourcesDir = {"{{bundle_resources_dir}}",
+                                                     "bundle_resources_dir"};
+const Substitution SubstitutionBundleExecutableDir = {
+    "{{bundle_executable_dir}}", "bundle_executable_dir"};
+
+// Valid for compile_xcassets tool.
+const Substitution SubstitutionBundleProductType = {"{{bundle_product_type}}",
+                                                    "product_type"};
+const Substitution SubstitutionBundlePartialInfoPlist = {
+    "{{bundle_partial_info_plist}}", "partial_info_plist"};
+const Substitution SubstitutionXcassetsCompilerFlags = {
+    "{{xcasset_compiler_flags}}", "xcasset_compiler_flags"};
+
+// Used only for the args of actions.
+const Substitution SubstitutionRspFileName = {"{{response_file_name}}",
+                                              "rspfile"};
+
+SubstitutionBits::SubstitutionBits() = default;
+
+void SubstitutionBits::MergeFrom(const SubstitutionBits& other) {
+  for (const Substitution* s : other.used)
+    used.insert(s);
+}
+
+void SubstitutionBits::FillVector(
+    std::vector<const Substitution*>* vect) const {
+  for (const Substitution* s : used) {
+    vect->push_back(s);
+  }
+}
+
+bool SubstitutionIsInOutputDir(const Substitution* type) {
+  return type == &SubstitutionSourceGenDir ||
+         type == &SubstitutionSourceOutDir || type == &SubstitutionRootGenDir ||
+         type == &SubstitutionRootOutDir || type == &SubstitutionTargetGenDir ||
+         type == &SubstitutionTargetOutDir;
+}
+
+bool SubstitutionIsInBundleDir(const Substitution* type) {
+  return type == &SubstitutionBundleRootDir ||
+         type == &SubstitutionBundleContentsDir ||
+         type == &SubstitutionBundleResourcesDir ||
+         type == &SubstitutionBundleExecutableDir;
+}
+
+bool IsValidBundleDataSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral ||
+         type == &SubstitutionSourceTargetRelative ||
+         type == &SubstitutionSourceNamePart ||
+         type == &SubstitutionSourceFilePart ||
+         type == &SubstitutionSourceRootRelativeDir ||
+         type == &SubstitutionBundleRootDir ||
+         type == &SubstitutionBundleContentsDir ||
+         type == &SubstitutionBundleResourcesDir ||
+         type == &SubstitutionBundleExecutableDir;
+}
+
+bool IsValidSourceSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral || type == &SubstitutionSource ||
+         type == &SubstitutionSourceNamePart ||
+         type == &SubstitutionSourceFilePart ||
+         type == &SubstitutionSourceDir ||
+         type == &SubstitutionSourceRootRelativeDir ||
+         type == &SubstitutionSourceGenDir ||
+         type == &SubstitutionSourceOutDir ||
+         type == &SubstitutionSourceTargetRelative;
+}
+
+bool IsValidScriptArgsSubstitution(const Substitution* type) {
+  return IsValidSourceSubstitution(type) || type == &SubstitutionRspFileName;
+}
+
+bool IsValidToolSubstitution(const Substitution* type) {
+  return type == &SubstitutionLiteral || type == &SubstitutionOutput ||
+         type == &SubstitutionLabel || type == &SubstitutionLabelName ||
+         type == &SubstitutionLabelNoToolchain ||
+         type == &SubstitutionRootGenDir || type == &SubstitutionRootOutDir ||
+         type == &SubstitutionTargetGenDir ||
+         type == &SubstitutionTargetOutDir ||
+         type == &SubstitutionTargetOutputName;
+}
+
+bool IsValidCopySubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &SubstitutionSource;
+}
+
+bool IsValidCompileXCassetsSubstitution(const Substitution* type) {
+  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+         type == &SubstitutionBundleProductType ||
+         type == &SubstitutionBundlePartialInfoPlist ||
+         type == &SubstitutionXcassetsCompilerFlags;
+}
+
+bool EnsureValidSubstitutions(const std::vector<const Substitution*>& types,
+                              bool (*is_valid_subst)(const Substitution*),
+                              const ParseNode* origin,
+                              Err* err) {
+  for (const Substitution* type : types) {
+    if (!is_valid_subst(type)) {
+      *err = Err(origin, "Invalid substitution type.",
+                 "The substitution " + std::string(type->name) +
+                     " isn't valid for something\n"
+                     "operating on a source file such as this.");
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/src/gn/substitution_type.h b/src/gn/substitution_type.h
new file mode 100644 (file)
index 0000000..28f6c66
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SUBSTITUTION_TYPE_H_
+#define TOOLS_GN_SUBSTITUTION_TYPE_H_
+
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+
+class Err;
+class ParseNode;
+
+// Each pair here represents the string representation of the substitution in GN
+// and in Ninja.
+struct Substitution {
+  const char* name;
+  const char* ninja_name;
+  DISALLOW_COPY_AND_ASSIGN(Substitution);
+};
+
+using SubstitutionTypes = const std::vector<const Substitution*>;
+
+// All possible substitutions, organized into logical sets.
+extern const std::vector<SubstitutionTypes*> AllSubstitutions;
+
+// The set of substitutions available to all tools.
+extern const SubstitutionTypes GeneralSubstitutions;
+
+// Types of substitutions.
+extern const Substitution SubstitutionLiteral;
+
+// Valid for all tools. These depend on the target and
+// do not vary on a per-file basis.
+extern const Substitution SubstitutionOutput;
+extern const Substitution SubstitutionLabel;
+extern const Substitution SubstitutionLabelName;
+extern const Substitution SubstitutionLabelNoToolchain;
+extern const Substitution SubstitutionRootGenDir;
+extern const Substitution SubstitutionRootOutDir;
+extern const Substitution SubstitutionOutputDir;
+extern const Substitution SubstitutionOutputExtension;
+extern const Substitution SubstitutionTargetGenDir;
+extern const Substitution SubstitutionTargetOutDir;
+extern const Substitution SubstitutionTargetOutputName;
+
+// Valid for all compiler tools.
+extern const Substitution SubstitutionSource;
+extern const Substitution SubstitutionSourceNamePart;
+extern const Substitution SubstitutionSourceFilePart;
+extern const Substitution SubstitutionSourceDir;
+extern const Substitution SubstitutionSourceRootRelativeDir;
+extern const Substitution SubstitutionSourceGenDir;
+extern const Substitution SubstitutionSourceOutDir;
+extern const Substitution SubstitutionSourceTargetRelative;
+
+// Valid for bundle_data targets.
+extern const Substitution SubstitutionBundleRootDir;
+extern const Substitution SubstitutionBundleContentsDir;
+extern const Substitution SubstitutionBundleResourcesDir;
+extern const Substitution SubstitutionBundleExecutableDir;
+
+// Valid for compile_xcassets tool.
+extern const Substitution SubstitutionBundleProductType;
+extern const Substitution SubstitutionBundlePartialInfoPlist;
+extern const Substitution SubstitutionXcassetsCompilerFlags;
+
+// Used only for the args of actions.
+extern const Substitution SubstitutionRspFileName;
+
+// A wrapper around an array if flags indicating whether a given substitution
+// type is required in some context. By convention, the LITERAL type bit is
+// not set.
+struct SubstitutionBits {
+  SubstitutionBits();
+
+  // Merges any bits set in the given "other" to this one. This object will
+  // then be the union of all bits in the two lists.
+  void MergeFrom(const SubstitutionBits& other);
+
+  // Converts the substitution type set to a vector of the types listed. Does
+  // not include SubstitutionLiteral.
+  void FillVector(std::vector<const Substitution*>* vect) const;
+
+  // This set depends on global uniqueness of pointers, and so all points in
+  // this set should be the Substitution* constants.
+  base::flat_set<const Substitution*> used;
+};
+
+// Returns true if the given substitution pattern references the output
+// directory. This is used to check strings that begin with a substitution to
+// verify that they produce a file in the output directory.
+bool SubstitutionIsInOutputDir(const Substitution* type);
+
+// Returns true if the given substitution pattern references the bundle
+// directory. This is used to check strings that begin with a substitution to
+// verify that they produce a file in the bundle directory.
+bool SubstitutionIsInBundleDir(const Substitution* type);
+
+// Returns true if the given substitution is valid for the named purpose.
+bool IsValidBundleDataSubstitution(const Substitution* type);
+bool IsValidSourceSubstitution(const Substitution* type);
+bool IsValidScriptArgsSubstitution(const Substitution* type);
+
+// Both compiler and linker tools.
+bool IsValidToolSubstitution(const Substitution* type);
+bool IsValidCopySubstitution(const Substitution* type);
+bool IsValidCompileXCassetsSubstitution(const Substitution* type);
+
+// Validates that each substitution type in the vector passes the given
+// is_valid_subst predicate. Returns true on success. On failure, fills in the
+// error object with an appropriate message and returns false.
+bool EnsureValidSubstitutions(const std::vector<const Substitution*>& types,
+                              bool (*is_valid_subst)(const Substitution*),
+                              const ParseNode* origin,
+                              Err* err);
+
+#endif  // TOOLS_GN_SUBSTITUTION_TYPE_H_
diff --git a/src/gn/substitution_writer.cc b/src/gn/substitution_writer.cc
new file mode 100644 (file)
index 0000000..c9624d7
--- /dev/null
@@ -0,0 +1,587 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/substitution_writer.h"
+
+#include "gn/build_settings.h"
+#include "gn/c_substitution_type.h"
+#include "gn/escape.h"
+#include "gn/filesystem_utils.h"
+#include "gn/output_file.h"
+#include "gn/rust_substitution_type.h"
+#include "gn/rust_tool.h"
+#include "gn/settings.h"
+#include "gn/source_file.h"
+#include "gn/string_utils.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/target.h"
+
+namespace {
+
+// Sets the given directory string to the destination, trimming any trailing
+// slash from the directory (SourceDirs and OutputFiles representing
+// directories will end in a trailing slash). If the directory is empty,
+// it will be replaced with a ".".
+void SetDirOrDotWithNoSlash(const std::string& dir, std::string* dest) {
+  if (!dir.empty() && dir[dir.size() - 1] == '/')
+    dest->assign(dir.data(), dir.size() - 1);
+  else
+    dest->assign(dir);
+
+  if (dest->empty())
+    dest->push_back('.');
+}
+
+}  // namespace
+
+const char kSourceExpansion_Help[] =
+    R"(How Source Expansion Works
+
+  Source expansion is used for the action_foreach and copy target types to map
+  source file names to output file names or arguments.
+
+  To perform source expansion in the outputs, GN maps every entry in the
+  sources to every entry in the outputs list, producing the cross product of
+  all combinations, expanding placeholders (see below).
+
+  Source expansion in the args works similarly, but performing the placeholder
+  substitution produces a different set of arguments for each invocation of the
+  script.
+
+  If no placeholders are found, the outputs or args list will be treated as a
+  static list of literal file names that do not depend on the sources.
+
+  See "gn help copy" and "gn help action_foreach" for more on how this is
+  applied.
+
+Placeholders
+
+  This section discusses only placeholders for actions. There are other
+  placeholders used in the definition of tools. See "gn help tool" for those.
+
+  {{source}}
+      The name of the source file including directory (*). This will generally
+      be used for specifying inputs to a script in the "args" variable.
+        "//foo/bar/baz.txt" => "../../foo/bar/baz.txt"
+
+  {{source_file_part}}
+      The file part of the source including the extension.
+        "//foo/bar/baz.txt" => "baz.txt"
+
+  {{source_name_part}}
+      The filename part of the source file with no directory or extension. This
+      will generally be used for specifying a transformation from a source file
+      to a destination file with the same name but different extension.
+        "//foo/bar/baz.txt" => "baz"
+
+  {{source_dir}}
+      The directory (*) containing the source file with no trailing slash.
+        "//foo/bar/baz.txt" => "../../foo/bar"
+
+  {{source_root_relative_dir}}
+      The path to the source file's directory relative to the source root, with
+      no leading "//" or trailing slashes. If the path is system-absolute,
+      (beginning in a single slash) this will just return the path with no
+      trailing slash. This value will always be the same, regardless of whether
+      it appears in the "outputs" or "args" section.
+        "//foo/bar/baz.txt" => "foo/bar"
+
+  {{source_gen_dir}}
+      The generated file directory (*) corresponding to the source file's path.
+      This will be different than the target's generated file directory if the
+      source file is in a different directory than the BUILD.gn file.
+        "//foo/bar/baz.txt" => "gen/foo/bar"
+
+  {{source_out_dir}}
+      The object file directory (*) corresponding to the source file's path,
+      relative to the build directory. this us be different than the target's
+      out directory if the source file is in a different directory than the
+      build.gn file.
+        "//foo/bar/baz.txt" => "obj/foo/bar"
+
+  {{source_target_relative}}
+      The path to the source file relative to the target's directory. This will
+      generally be used for replicating the source directory layout in the
+      output directory. This can only be used in actions and bundle_data
+      targets. It is an error to use in process_file_template where there is no
+      "target".
+        "//foo/bar/baz.txt" => "baz.txt"
+
+(*) Note on directories
+
+  Paths containing directories (except the source_root_relative_dir) will be
+  different depending on what context the expansion is evaluated in. Generally
+  it should "just work" but it means you can't concatenate strings containing
+  these values with reasonable results.
+
+  Details: source expansions can be used in the "outputs" variable, the "args"
+  variable, and in calls to "process_file_template". The "args" are passed to a
+  script which is run from the build directory, so these directories will
+  relative to the build directory for the script to find. In the other cases,
+  the directories will be source- absolute (begin with a "//") because the
+  results of those expansions will be handled by GN internally.
+
+Examples
+
+  Non-varying outputs:
+    action("hardcoded_outputs") {
+      sources = [ "input1.idl", "input2.idl" ]
+      outputs = [ "$target_out_dir/output1.dat",
+                  "$target_out_dir/output2.dat" ]
+    }
+  The outputs in this case will be the two literal files given.
+
+  Varying outputs:
+    action_foreach("varying_outputs") {
+      sources = [ "input1.idl", "input2.idl" ]
+      outputs = [ "{{source_gen_dir}}/{{source_name_part}}.h",
+                  "{{source_gen_dir}}/{{source_name_part}}.cc" ]
+    }
+  Performing source expansion will result in the following output names:
+    //out/Debug/obj/mydirectory/input1.h
+    //out/Debug/obj/mydirectory/input1.cc
+    //out/Debug/obj/mydirectory/input2.h
+    //out/Debug/obj/mydirectory/input2.cc
+)";
+
+// static
+void SubstitutionWriter::WriteWithNinjaVariables(
+    const SubstitutionPattern& pattern,
+    const EscapeOptions& escape_options,
+    std::ostream& out) {
+  // The result needs to be quoted as if it was one string, but the $ for
+  // the inserted Ninja variables can't be escaped. So write to a buffer with
+  // no quoting, and then quote the whole thing if necessary.
+  EscapeOptions no_quoting(escape_options);
+  no_quoting.inhibit_quoting = true;
+
+  bool needs_quotes = false;
+  std::string result;
+  for (const auto& range : pattern.ranges()) {
+    if (range.type == &SubstitutionLiteral) {
+      result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
+    } else {
+      result.append("${");
+      result.append(range.type->ninja_name);
+      result.append("}");
+    }
+  }
+
+  if (needs_quotes && !escape_options.inhibit_quoting)
+    out << "\"" << result << "\"";
+  else
+    out << result;
+}
+
+// static
+void SubstitutionWriter::GetListAsSourceFiles(const SubstitutionList& list,
+                                              std::vector<SourceFile>* output) {
+  for (const auto& pattern : list.list()) {
+    CHECK(pattern.ranges().size() == 1 &&
+          pattern.ranges()[0].type == &SubstitutionLiteral)
+        << "The substitution pattern \"" << pattern.AsString()
+        << "\" was expected to be a literal with no {{substitutions}}.";
+    const std::string& literal = pattern.ranges()[0].literal;
+    CHECK(literal.size() >= 1 && literal[0] == '/')
+        << "The result of the pattern \"" << pattern.AsString()
+        << "\" was not an absolute path.";
+    output->push_back(SourceFile(literal));
+  }
+}
+
+// static
+void SubstitutionWriter::GetListAsOutputFiles(const Settings* settings,
+                                              const SubstitutionList& list,
+                                              std::vector<OutputFile>* output) {
+  std::vector<SourceFile> output_as_sources;
+  GetListAsSourceFiles(list, &output_as_sources);
+  for (const auto& file : output_as_sources)
+    output->push_back(OutputFile(settings->build_settings(), file));
+}
+
+// static
+SourceFile SubstitutionWriter::ApplyPatternToSource(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionPattern& pattern,
+    const SourceFile& source) {
+  std::string result_value =
+      ApplyPatternToSourceAsString(target, settings, pattern, source);
+  CHECK(!result_value.empty() && result_value[0] == '/')
+      << "The result of the pattern \"" << pattern.AsString()
+      << "\" was not a path beginning in \"/\" or \"//\".";
+  return SourceFile(std::move(result_value));
+}
+
+// static
+std::string SubstitutionWriter::ApplyPatternToSourceAsString(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionPattern& pattern,
+    const SourceFile& source) {
+  std::string result_value;
+  for (const auto& subrange : pattern.ranges()) {
+    if (subrange.type == &SubstitutionLiteral) {
+      result_value.append(subrange.literal);
+    } else {
+      result_value.append(GetSourceSubstitution(target, settings, source,
+                                                subrange.type, OUTPUT_ABSOLUTE,
+                                                SourceDir()));
+    }
+  }
+  return result_value;
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionPattern& pattern,
+    const SourceFile& source) {
+  SourceFile result_as_source =
+      ApplyPatternToSource(target, settings, pattern, source);
+  return OutputFile(settings->build_settings(), result_as_source);
+}
+
+// static
+void SubstitutionWriter::ApplyListToSource(const Target* target,
+                                           const Settings* settings,
+                                           const SubstitutionList& list,
+                                           const SourceFile& source,
+                                           std::vector<SourceFile>* output) {
+  for (const auto& item : list.list())
+    output->push_back(ApplyPatternToSource(target, settings, item, source));
+}
+
+// static
+void SubstitutionWriter::ApplyListToSourceAsString(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionList& list,
+    const SourceFile& source,
+    std::vector<std::string>* output) {
+  for (const auto& item : list.list())
+    output->push_back(
+        ApplyPatternToSourceAsString(target, settings, item, source));
+}
+
+// static
+void SubstitutionWriter::ApplyListToSourceAsOutputFile(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionList& list,
+    const SourceFile& source,
+    std::vector<OutputFile>* output) {
+  for (const auto& item : list.list())
+    output->push_back(
+        ApplyPatternToSourceAsOutputFile(target, settings, item, source));
+}
+
+// static
+void SubstitutionWriter::ApplyListToSources(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionList& list,
+    const std::vector<SourceFile>& sources,
+    std::vector<SourceFile>* output) {
+  output->clear();
+  for (const auto& source : sources)
+    ApplyListToSource(target, settings, list, source, output);
+}
+
+// static
+void SubstitutionWriter::ApplyListToSourcesAsString(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionList& list,
+    const std::vector<SourceFile>& sources,
+    std::vector<std::string>* output) {
+  output->clear();
+  for (const auto& source : sources)
+    ApplyListToSourceAsString(target, settings, list, source, output);
+}
+
+// static
+void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
+    const Target* target,
+    const Settings* settings,
+    const SubstitutionList& list,
+    const std::vector<SourceFile>& sources,
+    std::vector<OutputFile>* output) {
+  output->clear();
+  for (const auto& source : sources)
+    ApplyListToSourceAsOutputFile(target, settings, list, source, output);
+}
+
+// static
+void SubstitutionWriter::WriteNinjaVariablesForSource(
+    const Target* target,
+    const Settings* settings,
+    const SourceFile& source,
+    const std::vector<const Substitution*>& types,
+    const EscapeOptions& escape_options,
+    std::ostream& out) {
+  for (const auto& type : types) {
+    // Don't write SOURCE since that just maps to Ninja's $in variable, which
+    // is implicit in the rule. RESPONSE_FILE_NAME is written separately
+    // only when writing target rules since it can never be used in any
+    // other context (like process_file_template).
+    if (type != &SubstitutionSource && type != &SubstitutionRspFileName) {
+      out << "  " << type->ninja_name << " = ";
+      EscapeStringToStream(
+          out,
+          GetSourceSubstitution(target, settings, source, type, OUTPUT_RELATIVE,
+                                settings->build_settings()->build_dir()),
+          escape_options);
+      out << std::endl;
+    }
+  }
+}
+
+// static
+std::string SubstitutionWriter::GetSourceSubstitution(
+    const Target* target,
+    const Settings* settings,
+    const SourceFile& source,
+    const Substitution* type,
+    OutputStyle output_style,
+    const SourceDir& relative_to) {
+  std::string to_rebase;
+  if (type == &SubstitutionSource) {
+    if (source.is_system_absolute())
+      return source.value();
+    to_rebase = source.value();
+  } else if (type == &SubstitutionSourceNamePart) {
+    return std::string(FindFilenameNoExtension(&source.value()));
+  } else if (type == &SubstitutionSourceFilePart) {
+    return source.GetName();
+  } else if (type == &SubstitutionSourceDir) {
+    if (source.is_system_absolute())
+      return DirectoryWithNoLastSlash(source.GetDir());
+    to_rebase = DirectoryWithNoLastSlash(source.GetDir());
+  } else if (type == &SubstitutionSourceRootRelativeDir) {
+    if (source.is_system_absolute())
+      return DirectoryWithNoLastSlash(source.GetDir());
+    return RebasePath(DirectoryWithNoLastSlash(source.GetDir()),
+                      SourceDir("//"),
+                      settings->build_settings()->root_path_utf8());
+  } else if (type == &SubstitutionSourceGenDir) {
+    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
+  } else if (type == &SubstitutionSourceOutDir) {
+    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
+  } else if (type == &SubstitutionSourceTargetRelative) {
+    if (target) {
+      return RebasePath(source.value(), target->label().dir(),
+                        settings->build_settings()->root_path_utf8());
+    }
+    NOTREACHED() << "Cannot use substitution " << type->name
+                 << " without target";
+    return std::string();
+  } else if (IsValidRustSubstitution(type)) {
+    to_rebase = source.value();
+  } else {
+    NOTREACHED() << "Unsupported substitution for this function: "
+                 << type->name;
+    return std::string();
+  }
+
+  // If we get here, the result is a path that should be made relative or
+  // absolute according to the output_style. Other cases (just file name or
+  // extension extraction) will have been handled via early return above.
+  if (output_style == OUTPUT_ABSOLUTE)
+    return to_rebase;
+  return RebasePath(to_rebase, relative_to,
+                    settings->build_settings()->root_path_utf8());
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile(
+    const Target* target,
+    const Tool* tool,
+    const SubstitutionPattern& pattern) {
+  std::string result_value;
+  for (const auto& subrange : pattern.ranges()) {
+    if (subrange.type == &SubstitutionLiteral) {
+      result_value.append(subrange.literal);
+    } else {
+      std::string subst;
+      CHECK(GetTargetSubstitution(target, subrange.type, &subst));
+      result_value.append(subst);
+    }
+  }
+  return OutputFile(result_value);
+}
+
+// static
+void SubstitutionWriter::ApplyListToTargetAsOutputFile(
+    const Target* target,
+    const Tool* tool,
+    const SubstitutionList& list,
+    std::vector<OutputFile>* output) {
+  for (const auto& item : list.list())
+    output->push_back(ApplyPatternToTargetAsOutputFile(target, tool, item));
+}
+
+// static
+bool SubstitutionWriter::GetTargetSubstitution(const Target* target,
+                                               const Substitution* type,
+                                               std::string* result) {
+  if (type == &SubstitutionLabel) {
+    // Only include the toolchain for non-default toolchains.
+    *result =
+        target->label().GetUserVisibleName(!target->settings()->is_default());
+  } else if (type == &SubstitutionLabelName) {
+    *result = target->label().name();
+  } else if (type == &SubstitutionLabelNoToolchain) {
+    *result = target->label().GetUserVisibleName(false);
+  } else if (type == &SubstitutionRootGenDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
+            .value(),
+        result);
+  } else if (type == &SubstitutionRootOutDir) {
+    SetDirOrDotWithNoSlash(
+        target->settings()->toolchain_output_subdir().value(), result);
+  } else if (type == &SubstitutionTargetGenDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
+        result);
+  } else if (type == &SubstitutionTargetOutDir) {
+    SetDirOrDotWithNoSlash(
+        GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
+        result);
+  } else if (type == &SubstitutionTargetOutputName) {
+    *result = target->GetComputedOutputName();
+  } else {
+    return false;
+  }
+  return true;
+}
+
+// static
+std::string SubstitutionWriter::GetTargetSubstitution(
+    const Target* target,
+    const Substitution* type) {
+  std::string result;
+  GetTargetSubstitution(target, type, &result);
+  return result;
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
+    const Target* target,
+    const SourceFile& source,
+    const SubstitutionPattern& pattern) {
+  OutputFile result;
+  for (const auto& subrange : pattern.ranges()) {
+    if (subrange.type == &SubstitutionLiteral) {
+      result.value().append(subrange.literal);
+    } else {
+      result.value().append(
+          GetCompilerSubstitution(target, source, subrange.type));
+    }
+  }
+  return result;
+}
+
+// static
+void SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+    const Target* target,
+    const SourceFile& source,
+    const SubstitutionList& list,
+    std::vector<OutputFile>* output) {
+  for (const auto& item : list.list())
+    output->push_back(ApplyPatternToCompilerAsOutputFile(target, source, item));
+}
+
+// static
+std::string SubstitutionWriter::GetCompilerSubstitution(
+    const Target* target,
+    const SourceFile& source,
+    const Substitution* type) {
+  // First try the common tool ones.
+  std::string result;
+  if (GetTargetSubstitution(target, type, &result))
+    return result;
+
+  // Fall-through to the source ones.
+  return GetSourceSubstitution(
+      target, target->settings(), source, type, OUTPUT_RELATIVE,
+      target->settings()->build_settings()->build_dir());
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+    const Target* target,
+    const Tool* tool,
+    const SubstitutionPattern& pattern) {
+  OutputFile result;
+  for (const auto& subrange : pattern.ranges()) {
+    if (subrange.type == &SubstitutionLiteral) {
+      result.value().append(subrange.literal);
+    } else {
+      result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
+    }
+  }
+  return result;
+}
+
+// static
+void SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+    const Target* target,
+    const Tool* tool,
+    const SubstitutionList& list,
+    std::vector<OutputFile>* output) {
+  for (const auto& item : list.list())
+    output->push_back(ApplyPatternToLinkerAsOutputFile(target, tool, item));
+}
+
+// static
+std::string SubstitutionWriter::GetLinkerSubstitution(
+    const Target* target,
+    const Tool* tool,
+    const Substitution* type) {
+  // First try the common tool ones.
+  std::string result;
+  if (GetTargetSubstitution(target, type, &result))
+    return result;
+
+  // Fall-through to the linker-specific ones.
+  if (type == &SubstitutionOutputDir) {
+    // Use the target's value if there is one (it will have no expansion
+    // patterns since it can directly use GN variables to compute whatever
+    // path it wants), or the tool's default (which will contain further
+    // expansions).
+    if (target->output_dir().is_null()) {
+      return ApplyPatternToLinkerAsOutputFile(target, tool,
+                                              tool->default_output_dir())
+          .value();
+    }
+    SetDirOrDotWithNoSlash(
+        RebasePath(target->output_dir().value(),
+                   target->settings()->build_settings()->build_dir()),
+        &result);
+    return result;
+  } else if (type == &SubstitutionOutputExtension) {
+    // Use the extension provided on the target if specified, otherwise
+    // fall back on the default. Note that the target's output extension
+    // does not include the dot but the tool's does.
+    if (!target->output_extension_set())
+      return tool->default_output_extension();
+    if (target->output_extension().empty())
+      return std::string();  // Explicitly set to no extension.
+    return std::string(".") + target->output_extension();
+  } else if (type == &kRustSubstitutionCrateName) {
+    // Only include the toolchain for non-default toolchains.
+    return target->rust_values().crate_name();
+  } else if (type == &CSubstitutionSwiftModuleName) {
+    return target->swift_values().module_name();
+  } else {
+    NOTREACHED();
+    return std::string();
+  }
+}
diff --git a/src/gn/substitution_writer.h b/src/gn/substitution_writer.h
new file mode 100644 (file)
index 0000000..95e4b94
--- /dev/null
@@ -0,0 +1,238 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SUBSTITUTION_WRITER_H_
+#define TOOLS_GN_SUBSTITUTION_WRITER_H_
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "gn/substitution_type.h"
+
+struct EscapeOptions;
+class OutputFile;
+class Settings;
+class SourceDir;
+class SourceFile;
+class SubstitutionList;
+class SubstitutionPattern;
+class Target;
+class Tool;
+
+// Help text for script source expansion.
+extern const char kSourceExpansion_Help[];
+
+// This class handles writing or applying substitution patterns to strings.
+//
+// There are several different uses:
+//
+//  - Source substitutions: These are used to compute action_foreach
+//    outputs and arguments. Functions are provided to expand these in terms
+//    of both OutputFiles (for writing Ninja files) as well as SourceFiles
+//    (for computing lists used by code).
+//
+//  - Target substitutions: These are specific to the target+tool combination
+//    and are shared between the compiler and linker ones. It includes things
+//    like the target_gen_dir.
+//
+//  - Compiler substitutions: These are used to compute compiler outputs.
+//    It includes all source substitutions (since they depend on the various
+//    parts of the source file) as well as the target substitutions.
+//
+//  - Linker substitutions: These are used to compute linker outputs. It
+//    includes the target substitutions.
+//
+// The compiler and linker specific substitutions do NOT include the various
+// cflags, ldflags, libraries, etc. These are written by the ninja target
+// writer since they depend on traversing the dependency tree.
+//
+// The methods which take a target as an argument can accept null target
+// pointer if there is no target context, in which case the substitutions
+// requiring target context will not work.
+class SubstitutionWriter {
+ public:
+  enum OutputStyle {
+    OUTPUT_ABSOLUTE,  // Dirs will be absolute "//foo/bar".
+    OUTPUT_RELATIVE,  // Dirs will be relative to a given directory.
+  };
+
+  // Writes the pattern to the given stream with no special handling, and with
+  // Ninja variables replacing the patterns.
+  static void WriteWithNinjaVariables(const SubstitutionPattern& pattern,
+                                      const EscapeOptions& escape_options,
+                                      std::ostream& out);
+
+  // NOP substitutions ---------------------------------------------------------
+
+  // Converts the given SubstitutionList to OutputFiles assuming there are
+  // no substitutions (it will assert if there are). This is used for cases
+  // like actions where the outputs are explicit, but the list is stored as
+  // a SubstitutionList.
+  static void GetListAsSourceFiles(const SubstitutionList& list,
+                                   std::vector<SourceFile>* output);
+  static void GetListAsOutputFiles(const Settings* settings,
+                                   const SubstitutionList& list,
+                                   std::vector<OutputFile>* output);
+
+  // Source substitutions -----------------------------------------------------
+
+  // Applies the substitution pattern to a source file, returning the result
+  // as either a string, a SourceFile or an OutputFile. If the result is
+  // expected to be a SourceFile or an OutputFile, this will CHECK if the
+  // result isn't in the correct directory. The caller should validate this
+  // first (see for example IsFileInOuputDir).
+  //
+  // The target can be null (see class comment above).
+  static SourceFile ApplyPatternToSource(const Target* target,
+                                         const Settings* settings,
+                                         const SubstitutionPattern& pattern,
+                                         const SourceFile& source);
+  static std::string ApplyPatternToSourceAsString(
+      const Target* target,
+      const Settings* settings,
+      const SubstitutionPattern& pattern,
+      const SourceFile& source);
+  static OutputFile ApplyPatternToSourceAsOutputFile(
+      const Target* target,
+      const Settings* settings,
+      const SubstitutionPattern& pattern,
+      const SourceFile& source);
+
+  // Applies the substitution list to a source, APPENDING the result to the
+  // given output vector. It works this way so one can call multiple times to
+  // apply to multiple files and create a list. The result can either be
+  // SourceFiles or OutputFiles.
+  //
+  // The target can be null (see class comment above).
+  static void ApplyListToSource(const Target* target,
+                                const Settings* settings,
+                                const SubstitutionList& list,
+                                const SourceFile& source,
+                                std::vector<SourceFile>* output);
+  static void ApplyListToSourceAsString(const Target* target,
+                                        const Settings* settings,
+                                        const SubstitutionList& list,
+                                        const SourceFile& source,
+                                        std::vector<std::string>* output);
+  static void ApplyListToSourceAsOutputFile(const Target* target,
+                                            const Settings* settings,
+                                            const SubstitutionList& list,
+                                            const SourceFile& source,
+                                            std::vector<OutputFile>* output);
+
+  // Like ApplyListToSource but applies the list to all sources and replaces
+  // rather than appends the output (this produces the complete output).
+  //
+  // The target can be null (see class comment above).
+  static void ApplyListToSources(const Target* target,
+                                 const Settings* settings,
+                                 const SubstitutionList& list,
+                                 const std::vector<SourceFile>& sources,
+                                 std::vector<SourceFile>* output);
+  static void ApplyListToSourcesAsString(const Target* target,
+                                         const Settings* settings,
+                                         const SubstitutionList& list,
+                                         const std::vector<SourceFile>& sources,
+                                         std::vector<std::string>* output);
+  static void ApplyListToSourcesAsOutputFile(
+      const Target* target,
+      const Settings* settings,
+      const SubstitutionList& list,
+      const std::vector<SourceFile>& sources,
+      std::vector<OutputFile>* output);
+
+  // Given a list of source replacement types used, writes the Ninja variable
+  // definitions for the given source file to use for those replacements. The
+  // variables will be indented two spaces. Since this is for writing to
+  // Ninja files, paths will be relative to the build dir, and no definition
+  // for {{source}} will be written since that maps to Ninja's implicit $in
+  // variable.
+  //
+  // The target can be null (see class comment above).
+  static void WriteNinjaVariablesForSource(
+      const Target* target,
+      const Settings* settings,
+      const SourceFile& source,
+      const std::vector<const Substitution*>& types,
+      const EscapeOptions& escape_options,
+      std::ostream& out);
+
+  // Extracts the given type of substitution related to a source file from the
+  // given source file. If output_style is OUTPUT_RELATIVE, relative_to
+  // indicates the directory that the relative directories should be relative
+  // to, otherwise it is ignored.
+  //
+  // The target can be null (see class comment above).
+  static std::string GetSourceSubstitution(const Target* target,
+                                           const Settings* settings,
+                                           const SourceFile& source,
+                                           const Substitution* type,
+                                           OutputStyle output_style,
+                                           const SourceDir& relative_to);
+
+  // Target substitutions ------------------------------------------------------
+  //
+  // Handles the target substitutions that apply to both compiler and linker
+  // tools.
+  static OutputFile ApplyPatternToTargetAsOutputFile(
+      const Target* target,
+      const Tool* tool,
+      const SubstitutionPattern& pattern);
+  static void ApplyListToTargetAsOutputFile(const Target* target,
+                                            const Tool* tool,
+                                            const SubstitutionList& list,
+                                            std::vector<OutputFile>* output);
+
+  // This function is slightly different than the other substitution getters
+  // since it can handle failure (since it is designed to be used by the
+  // compiler and linker ones which will fall through if it's not a common tool
+  // one).
+  static bool GetTargetSubstitution(const Target* target,
+                                    const Substitution* type,
+                                    std::string* result);
+  static std::string GetTargetSubstitution(const Target* target,
+                                           const Substitution* type);
+
+  // Compiler substitutions ----------------------------------------------------
+  //
+  // A compiler substitution allows both source and tool substitutions. These
+  // are used to compute output names for compiler tools.
+
+  static OutputFile ApplyPatternToCompilerAsOutputFile(
+      const Target* target,
+      const SourceFile& source,
+      const SubstitutionPattern& pattern);
+  static void ApplyListToCompilerAsOutputFile(const Target* target,
+                                              const SourceFile& source,
+                                              const SubstitutionList& list,
+                                              std::vector<OutputFile>* output);
+
+  // Like GetSourceSubstitution but for strings based on the target or
+  // toolchain. This type of result will always be relative to the build
+  // directory.
+  static std::string GetCompilerSubstitution(const Target* target,
+                                             const SourceFile& source,
+                                             const Substitution* type);
+
+  // Linker substitutions ------------------------------------------------------
+
+  static OutputFile ApplyPatternToLinkerAsOutputFile(
+      const Target* target,
+      const Tool* tool,
+      const SubstitutionPattern& pattern);
+  static void ApplyListToLinkerAsOutputFile(const Target* target,
+                                            const Tool* tool,
+                                            const SubstitutionList& list,
+                                            std::vector<OutputFile>* output);
+
+  // Like GetSourceSubstitution but for strings based on the target or
+  // toolchain. This type of result will always be relative to the build
+  // directory.
+  static std::string GetLinkerSubstitution(const Target* target,
+                                           const Tool* tool,
+                                           const Substitution* type);
+};
+
+#endif  // TOOLS_GN_SUBSTITUTION_WRITER_H_
diff --git a/src/gn/substitution_writer_unittest.cc b/src/gn/substitution_writer_unittest.cc
new file mode 100644 (file)
index 0000000..fc3c446
--- /dev/null
@@ -0,0 +1,325 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "gn/c_substitution_type.h"
+#include "gn/err.h"
+#include "gn/escape.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(SubstitutionWriter, GetListAs) {
+  TestWithScope setup;
+
+  SubstitutionList list =
+      SubstitutionList::MakeForTest("//foo/bar/a.cc", "//foo/bar/b.cc");
+
+  std::vector<SourceFile> sources;
+  SubstitutionWriter::GetListAsSourceFiles(list, &sources);
+  ASSERT_EQ(2u, sources.size());
+  EXPECT_EQ("//foo/bar/a.cc", sources[0].value());
+  EXPECT_EQ("//foo/bar/b.cc", sources[1].value());
+
+  std::vector<OutputFile> outputs;
+  SubstitutionWriter::GetListAsOutputFiles(setup.settings(), list, &outputs);
+  ASSERT_EQ(2u, outputs.size());
+  EXPECT_EQ("../../foo/bar/a.cc", outputs[0].value());
+  EXPECT_EQ("../../foo/bar/b.cc", outputs[1].value());
+}
+
+TEST(SubstitutionWriter, ApplyPatternToSource) {
+  TestWithScope setup;
+
+  SubstitutionPattern pattern;
+  Err err;
+  ASSERT_TRUE(pattern.Parse("{{source_gen_dir}}/{{source_name_part}}.tmp",
+                            nullptr, &err));
+
+  SourceFile result = SubstitutionWriter::ApplyPatternToSource(
+      nullptr, setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
+  ASSERT_EQ("//out/Debug/gen/foo/bar/myfile.tmp", result.value());
+}
+
+TEST(SubstitutionWriter, ApplyPatternToSourceAsOutputFile) {
+  TestWithScope setup;
+
+  SubstitutionPattern pattern;
+  Err err;
+  ASSERT_TRUE(pattern.Parse("{{source_gen_dir}}/{{source_name_part}}.tmp",
+                            nullptr, &err));
+
+  OutputFile result = SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
+      nullptr, setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
+  ASSERT_EQ("gen/foo/bar/myfile.tmp", result.value());
+}
+
+TEST(SubstitutionWriter, WriteNinjaVariablesForSource) {
+  TestWithScope setup;
+
+  std::vector<const Substitution*> types;
+  types.push_back(&SubstitutionSource);
+  types.push_back(&SubstitutionSourceNamePart);
+  types.push_back(&SubstitutionSourceDir);
+
+  EscapeOptions options;
+  options.mode = ESCAPE_NONE;
+
+  std::ostringstream out;
+  SubstitutionWriter::WriteNinjaVariablesForSource(
+      nullptr, setup.settings(), SourceFile("//foo/bar/baz.txt"), types,
+      options, out);
+
+  // The "source" should be skipped since that will expand to $in which is
+  // implicit.
+  EXPECT_EQ(
+      "  source_name_part = baz\n"
+      "  source_dir = ../../foo/bar\n",
+      out.str());
+}
+
+TEST(SubstitutionWriter, WriteWithNinjaVariables) {
+  Err err;
+  SubstitutionPattern pattern;
+  ASSERT_TRUE(pattern.Parse("-i {{source}} --out=bar\"{{source_name_part}}\".o",
+                            nullptr, &err));
+  EXPECT_FALSE(err.has_error());
+
+  EscapeOptions options;
+  options.mode = ESCAPE_NONE;
+
+  std::ostringstream out;
+  SubstitutionWriter::WriteWithNinjaVariables(pattern, options, out);
+
+  EXPECT_EQ("-i ${in} --out=bar\"${source_name_part}\".o", out.str());
+}
+
+TEST(SubstitutionWriter, SourceSubstitutions) {
+  TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+// Call to get substitutions relative to the build dir.
+#define GetRelSubst(str, what)                          \
+  SubstitutionWriter::GetSourceSubstitution(            \
+      &target, setup.settings(), SourceFile(str), what, \
+      SubstitutionWriter::OUTPUT_RELATIVE,              \
+      setup.settings()->build_settings()->build_dir())
+
+// Call to get absolute directory substitutions.
+#define GetAbsSubst(str, what)                          \
+  SubstitutionWriter::GetSourceSubstitution(            \
+      &target, setup.settings(), SourceFile(str), what, \
+      SubstitutionWriter::OUTPUT_ABSOLUTE, SourceDir())
+
+  // Try all possible templates with a normal looking string.
+  EXPECT_EQ("../../foo/bar/baz.txt",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSource));
+  EXPECT_EQ("//foo/bar/baz.txt",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSource));
+
+  EXPECT_EQ("baz",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceNamePart));
+  EXPECT_EQ("baz",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceNamePart));
+
+  EXPECT_EQ("baz.txt",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceFilePart));
+  EXPECT_EQ("baz.txt",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceFilePart));
+
+  EXPECT_EQ("../../foo/bar",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceDir));
+  EXPECT_EQ("//foo/bar",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceDir));
+
+  EXPECT_EQ("foo/bar", GetRelSubst("//foo/bar/baz.txt",
+                                   &SubstitutionSourceRootRelativeDir));
+  EXPECT_EQ("foo/bar", GetAbsSubst("//foo/bar/baz.txt",
+                                   &SubstitutionSourceRootRelativeDir));
+
+  EXPECT_EQ("gen/foo/bar",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceGenDir));
+  EXPECT_EQ("//out/Debug/gen/foo/bar",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceGenDir));
+
+  EXPECT_EQ("obj/foo/bar",
+            GetRelSubst("//foo/bar/baz.txt", &SubstitutionSourceOutDir));
+  EXPECT_EQ("//out/Debug/obj/foo/bar",
+            GetAbsSubst("//foo/bar/baz.txt", &SubstitutionSourceOutDir));
+
+  // Operations on an absolute path.
+  EXPECT_EQ("/baz.txt", GetRelSubst("/baz.txt", &SubstitutionSource));
+  EXPECT_EQ("/.", GetRelSubst("/baz.txt", &SubstitutionSourceDir));
+  EXPECT_EQ("gen/ABS_PATH", GetRelSubst("/baz.txt", &SubstitutionSourceGenDir));
+  EXPECT_EQ("obj/ABS_PATH", GetRelSubst("/baz.txt", &SubstitutionSourceOutDir));
+#if defined(OS_WIN)
+  EXPECT_EQ("gen/ABS_PATH/C",
+            GetRelSubst("/C:/baz.txt", &SubstitutionSourceGenDir));
+  EXPECT_EQ("obj/ABS_PATH/C",
+            GetRelSubst("/C:/baz.txt", &SubstitutionSourceOutDir));
+#endif
+
+  EXPECT_EQ(".", GetRelSubst("//baz.txt", &SubstitutionSourceRootRelativeDir));
+
+  EXPECT_EQ("baz.txt", GetRelSubst("//foo/bar/baz.txt",
+                                   &SubstitutionSourceTargetRelative));
+  EXPECT_EQ("baz.txt", GetAbsSubst("//foo/bar/baz.txt",
+                                   &SubstitutionSourceTargetRelative));
+
+#undef GetAbsSubst
+#undef GetRelSubst
+}
+
+TEST(SubstitutionWriter, TargetSubstitutions) {
+  TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::string result;
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionLabel, &result));
+  EXPECT_EQ("//foo/bar:baz", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionLabelName, &result));
+  EXPECT_EQ("baz", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionLabelNoToolchain, &result));
+  EXPECT_EQ("//foo/bar:baz", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionRootGenDir, &result));
+  EXPECT_EQ("gen", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionRootOutDir, &result));
+  EXPECT_EQ(".", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionTargetGenDir, &result));
+  EXPECT_EQ("gen/foo/bar", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionTargetOutDir, &result));
+  EXPECT_EQ("obj/foo/bar", result);
+
+  EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+      &target, &SubstitutionTargetOutputName, &result));
+  EXPECT_EQ("libbaz", result);
+}
+
+TEST(SubstitutionWriter, CompilerSubstitutions) {
+  TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // The compiler substitution is just source + target combined. So test one
+  // of each of those classes of things to make sure this is hooked up.
+  EXPECT_EQ("file", SubstitutionWriter::GetCompilerSubstitution(
+                        &target, SourceFile("//foo/bar/file.txt"),
+                        &SubstitutionSourceNamePart));
+  EXPECT_EQ("gen/foo/bar", SubstitutionWriter::GetCompilerSubstitution(
+                               &target, SourceFile("//foo/bar/file.txt"),
+                               &SubstitutionTargetGenDir));
+}
+
+TEST(SubstitutionWriter, LinkerSubstitutions) {
+  TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const Tool* tool = setup.toolchain()->GetToolForTargetFinalOutput(&target);
+
+  // The compiler substitution is just target + OUTPUT_EXTENSION combined. So
+  // test one target one plus the output extension.
+  EXPECT_EQ(".so", SubstitutionWriter::GetLinkerSubstitution(
+                       &target, tool, &SubstitutionOutputExtension));
+  EXPECT_EQ("gen/foo/bar", SubstitutionWriter::GetLinkerSubstitution(
+                               &target, tool, &SubstitutionTargetGenDir));
+
+  // Test that we handle paths that end up in the root build dir properly
+  // (no leading "./" or "/").
+  SubstitutionPattern pattern;
+  ASSERT_TRUE(pattern.Parse("{{root_out_dir}}/{{target_output_name}}.so",
+                            nullptr, &err));
+
+  OutputFile output = SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+      &target, tool, pattern);
+  EXPECT_EQ("./libbaz.so", output.value());
+
+  // Output extensions can be overridden.
+  target.set_output_extension("extension");
+  EXPECT_EQ(".extension", SubstitutionWriter::GetLinkerSubstitution(
+                              &target, tool, &SubstitutionOutputExtension));
+  target.set_output_extension("");
+  EXPECT_EQ("", SubstitutionWriter::GetLinkerSubstitution(
+                    &target, tool, &SubstitutionOutputExtension));
+
+  // Output directory is tested in a separate test below.
+}
+
+TEST(SubstitutionWriter, OutputDir) {
+  TestWithScope setup;
+  Err err;
+
+  // This tool has an output directory pattern and uses that for the
+  // output name.
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolLink);
+  SubstitutionPattern out_dir_pattern;
+  ASSERT_TRUE(out_dir_pattern.Parse("{{root_out_dir}}/{{target_output_name}}",
+                                    nullptr, &err));
+  tool->set_default_output_dir(out_dir_pattern);
+  tool->SetComplete();
+
+  // Default target with no output dir overrides.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "baz"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // The output should expand the default from the patterns in the tool.
+  SubstitutionPattern output_name;
+  ASSERT_TRUE(output_name.Parse("{{output_dir}}/{{target_output_name}}.exe",
+                                nullptr, &err));
+  EXPECT_EQ("./baz/baz.exe",
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                &target, tool.get(), output_name)
+                .value());
+
+  // Override the output name to the root build dir.
+  target.set_output_dir(SourceDir("//out/Debug/"));
+  EXPECT_EQ("./baz.exe", SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                             &target, tool.get(), output_name)
+                             .value());
+
+  // Override the output name to a new subdirectory.
+  target.set_output_dir(SourceDir("//out/Debug/foo/bar"));
+  EXPECT_EQ("foo/bar/baz.exe",
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                &target, tool.get(), output_name)
+                .value());
+}
diff --git a/src/gn/swift_values.cc b/src/gn/swift_values.cc
new file mode 100644 (file)
index 0000000..c15e319
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/swift_values.h"
+
+#include "gn/deps_iterator.h"
+#include "gn/err.h"
+#include "gn/settings.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+
+SwiftValues::SwiftValues() = default;
+
+SwiftValues::~SwiftValues() = default;
+
+bool SwiftValues::OnTargetResolved(const Target* target, Err* err) {
+  if (!FillModuleOuputFile(target, err))
+    return false;
+
+  FillModuleDependencies(target);
+  return true;
+}
+
+void SwiftValues::FillModuleDependencies(const Target* target) {
+  for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->toolchain() == target->toolchain() ||
+        pair.ptr->toolchain()->propagates_configs()) {
+      modules_.Append(pair.ptr->swift_values().public_modules().begin(),
+                      pair.ptr->swift_values().public_modules().end());
+    }
+  }
+
+  for (const auto& pair : target->public_deps()) {
+    if (pair.ptr->toolchain() == target->toolchain() ||
+        pair.ptr->toolchain()->propagates_configs())
+      public_modules_.Append(pair.ptr->swift_values().public_modules().begin(),
+                             pair.ptr->swift_values().public_modules().end());
+  }
+
+  if (builds_module())
+    public_modules_.push_back(target);
+}
+
+bool SwiftValues::FillModuleOuputFile(const Target* target, Err* err) {
+  if (!target->IsBinary() || !target->source_types_used().SwiftSourceUsed())
+    return true;
+
+  const Tool* tool =
+      target->toolchain()->GetToolForSourceType(SourceFile::SOURCE_SWIFT);
+  CHECK(tool->outputs().list().size() >= 1);
+
+  OutputFile module_output_file =
+      SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+          target, tool, tool->outputs().list()[0]);
+
+  const SourceFile module_output_file_as_source =
+      module_output_file.AsSourceFile(target->settings()->build_settings());
+  if (module_output_file_as_source.type() != SourceFile::SOURCE_SWIFTMODULE) {
+    *err = Err(tool->defined_from(), "Incorrect outputs for tool",
+               "The first output of tool " + std::string(tool->name()) +
+                   " must be a .swiftmodule file.");
+    return false;
+  }
+
+  module_output_file_ = std::move(module_output_file);
+  module_output_dir_ = module_output_file_as_source.GetDir();
+
+  return true;
+}
diff --git a/src/gn/swift_values.h b/src/gn/swift_values.h
new file mode 100644 (file)
index 0000000..c529738
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SWIFT_TARGET_VALUES_H_
+#define TOOLS_GN_SWIFT_TARGET_VALUES_H_
+
+#include <string>
+
+#include "gn/output_file.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/unique_vector.h"
+
+class Err;
+class Target;
+
+// Holds values specific to target that compile .swift files.
+class SwiftValues {
+ public:
+  SwiftValues();
+  ~SwiftValues();
+
+  SwiftValues(const SwiftValues&) = delete;
+  SwiftValues& operator=(const SwiftValues&) = delete;
+
+  // Called when the target is resolved.
+  bool OnTargetResolved(const Target* target, Err* err);
+
+  // Path of the bridging header.
+  SourceFile& bridge_header() { return bridge_header_; }
+  const SourceFile& bridge_header() const { return bridge_header_; }
+
+  // Name of the module.
+  std::string& module_name() { return module_name_; }
+  const std::string module_name() const { return module_name_; }
+
+  // Returns whether the target generates a .swiftmodule.
+  bool builds_module() const { return module_output_file_ != OutputFile(); }
+
+  // Name of the generated .swiftmodule file. Computed when the target
+  // is resolved.
+  const OutputFile& module_output_file() const { return module_output_file_; }
+
+  // Path of the directory containing the generated .swiftmodule file.
+  // Computed when the target is resolved.
+  const SourceDir& module_output_dir() const { return module_output_dir_; }
+
+  // List of dependent target that generate a .swiftmodule. The current target
+  // is assumed to depend on those modules, and will add them to the module
+  // search path.
+  const UniqueVector<const Target*>& modules() const { return modules_; }
+
+  // List of dependent target that generate a .swiftmodule that are publicly
+  // exported by the current target. This will include the current target if
+  // it generates a .swiftmodule.
+  const UniqueVector<const Target*>& public_modules() const {
+    return public_modules_;
+  }
+
+ private:
+  // Fill informations about .swiftmodule generated by this target.
+  bool FillModuleOuputFile(const Target* target, Err* err);
+
+  // Fill dependencies information on other target generating .swiftmodules.
+  void FillModuleDependencies(const Target* target);
+
+  // Name of the optional bridge header used to import Objective-C classes.
+  // Filled from the target, may be empty even if the target include .swift
+  // source files.
+  SourceFile bridge_header_;
+
+  // Name of the generate module for use by substitution.
+  std::string module_name_;
+
+  // Path to the .swiftmodule generated by this target. Will be empty if the
+  // target does not include .swift sources.
+  OutputFile module_output_file_;
+
+  // Path of the directory containing the .swiftmodule generated by this
+  // target. Will be null if the target does not include .swift sources.
+  SourceDir module_output_dir_;
+
+  // For modules() and public_modules() function. Will be filled when the
+  // target is resolved (can be non-empty even if the target does not build
+  // .swift sources due to transitive dependencies).
+  UniqueVector<const Target*> modules_;
+  UniqueVector<const Target*> public_modules_;
+};
+
+#endif  // TOOLS_GN_SWIFT_TARGET_VALUES_H_
diff --git a/src/gn/swift_values_generator.cc b/src/gn/swift_values_generator.cc
new file mode 100644 (file)
index 0000000..ba56e28
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/swift_values_generator.h"
+
+#include "gn/label.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/swift_values.h"
+#include "gn/swift_variables.h"
+#include "gn/target.h"
+#include "gn/value_extractors.h"
+
+SwiftValuesGenerator::SwiftValuesGenerator(Target* target,
+                                           Scope* scope,
+                                           Err* err)
+    : target_(target), scope_(scope), err_(err) {}
+
+SwiftValuesGenerator::~SwiftValuesGenerator() = default;
+
+void SwiftValuesGenerator::Run() {
+  if (!FillBridgeHeader())
+    return;
+
+  if (!FillModuleName())
+    return;
+}
+
+bool SwiftValuesGenerator::FillBridgeHeader() {
+  const Value* value = scope_->GetValue(variables::kSwiftBridgeHeader, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  SourceFile dest;
+  if (!ExtractRelativeFile(scope_->settings()->build_settings(), *value,
+                           scope_->GetSourceDir(), &dest, err_))
+    return false;
+
+  target_->swift_values().bridge_header() = std::move(dest);
+  return true;
+}
+
+bool SwiftValuesGenerator::FillModuleName() {
+  const Value* value = scope_->GetValue(variables::kSwiftModuleName, true);
+  if (!value) {
+    // The target name will be used.
+    target_->swift_values().module_name() = target_->label().name();
+    return true;
+  }
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  target_->swift_values().module_name() = std::move(value->string_value());
+  return true;
+}
diff --git a/src/gn/swift_values_generator.h b/src/gn/swift_values_generator.h
new file mode 100644 (file)
index 0000000..211c059
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SWIFT_TARGET_VALUES_GENERATOR_H_
+#define TOOLS_GN_SWIFT_TARGET_VALUES_GENERATOR_H_
+
+#include <string>
+
+class Err;
+class FunctionCallNode;
+class Scope;
+class Target;
+
+class SwiftValuesGenerator {
+ public:
+  SwiftValuesGenerator(Target* target, Scope* scope, Err* err);
+  ~SwiftValuesGenerator();
+
+  SwiftValuesGenerator(const SwiftValuesGenerator&) = delete;
+  SwiftValuesGenerator& operator=(const SwiftValuesGenerator&) = delete;
+
+  void Run();
+
+ private:
+  bool FillBridgeHeader();
+  bool FillModuleName();
+
+  Target* target_;
+  Scope* scope_;
+  Err* err_;
+};
+
+#endif  // TOOLS_GN_SWIFT_TARGET_VALUES_GENERATOR_H_
diff --git a/src/gn/swift_variables.cc b/src/gn/swift_variables.cc
new file mode 100644 (file)
index 0000000..cf9d3d4
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/swift_variables.h"
+
+namespace variables {
+
+// Swift target vars -----------------------------------------------------
+
+const char kSwiftBridgeHeader[] = "bridge_header";
+const char kSwiftBridgeHeader_HelpShort[] =
+    "bridge_header: [string] Path to C/Objective-C compatibility header.";
+const char kSwiftBridgeHeader_Help[] =
+    R"(bridge_header: [string] Path to C/Objective-C compatibility header.
+
+  Valid for binary targets that contain Swift sources.
+
+  Path to an header that includes C/Objective-C functions and types that
+  needs to be made available to the Swift module.
+)";
+
+const char kSwiftModuleName[] = "module_name";
+const char kSwiftModuleName_HelpShort[] =
+    "module_name: [string] The name for the compiled module.";
+const char kSwiftModuleName_Help[] =
+    R"(module_name: [string] The name for the compiled module.
+
+  Valid for binary targets that contain Swift sources.
+
+  If module_name is not set, then this rule will use the target name.
+)";
+
+void InsertSwiftVariables(VariableInfoMap* info_map) {
+  info_map->insert(std::make_pair(
+      kSwiftBridgeHeader,
+      VariableInfo(kSwiftBridgeHeader_HelpShort, kSwiftBridgeHeader_Help)));
+
+  info_map->insert(std::make_pair(
+      kSwiftModuleName,
+      VariableInfo(kSwiftModuleName_HelpShort, kSwiftModuleName_Help)));
+}
+
+}  // namespace variables
diff --git a/src/gn/swift_variables.h b/src/gn/swift_variables.h
new file mode 100644 (file)
index 0000000..c430468
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SWIFT_TARGET_VARIABLES_H_
+#define TOOLS_GN_SWIFT_TARGET_VARIABLES_H_
+
+#include "gn/variables.h"
+
+namespace variables {
+
+// Swift target vars -----------------------------------------------------
+
+extern const char kSwiftBridgeHeader[];
+extern const char kSwiftBridgeHeader_HelpShort[];
+extern const char kSwiftBridgeHeader_Help[];
+
+extern const char kSwiftModuleName[];
+extern const char kSwiftModuleName_HelpShort[];
+extern const char kSwiftModuleName_Help[];
+
+void InsertSwiftVariables(VariableInfoMap* info_map);
+
+}  // namespace variables
+
+#endif  // TOOLS_GN_SWIFT_TARGET_VARIABLES_H_
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
new file mode 100644 (file)
index 0000000..fb63b30
--- /dev/null
@@ -0,0 +1,356 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/switches.h"
+
+namespace switches {
+
+const char kArgs[] = "args";
+const char kArgs_HelpShort[] = "--args: Specifies build arguments overrides.";
+const char kArgs_Help[] =
+    R"(--args: Specifies build arguments overrides.
+
+  See "gn help buildargs" for an overview of how build arguments work.
+
+  Most operations take a build directory. The build arguments are taken from
+  the previous build done in that directory. If a command specifies --args, it
+  will override the previous arguments stored in the build directory, and use
+  the specified ones.
+
+  The args specified will be saved to the build directory for subsequent
+  commands. Specifying --args="" will clear all build arguments.
+
+Formatting
+
+  The value of the switch is interpreted in GN syntax. For typical usage of
+  string arguments, you will need to be careful about escaping of quotes.
+
+Examples
+
+  gn gen out/Default --args="foo=\"bar\""
+
+  gn gen out/Default --args='foo="bar" enable=true blah=7'
+
+  gn check out/Default --args=""
+    Clears existing build args from the directory.
+
+  gn desc out/Default --args="some_list=[1, false, \"foo\"]"
+)";
+
+#define COLOR_HELP_LONG                                                       \
+  "--[no]color: Forces colored output on or off.\n"                           \
+  "\n"                                                                        \
+  "  Normally GN will try to detect whether it is outputting to a terminal\n" \
+  "  and will enable or disable color accordingly. Use of these switches\n"   \
+  "  will override the default.\n"                                            \
+  "\n"                                                                        \
+  "Examples\n"                                                                \
+  "\n"                                                                        \
+  "  gn gen out/Default --color\n"                                            \
+  "\n"                                                                        \
+  "  gn gen out/Default --nocolor\n"
+const char kColor[] = "color";
+const char kColor_HelpShort[] = "--color: Force colored output.";
+const char kColor_Help[] = COLOR_HELP_LONG;
+
+const char kDotfile[] = "dotfile";
+const char kDotfile_HelpShort[] =
+    "--dotfile: Override the name of the \".gn\" file.";
+const char kDotfile_Help[] =
+    R"(--dotfile: Override the name of the ".gn" file.
+
+  Normally GN loads the ".gn" file from the source root for some basic
+  configuration (see "gn help dotfile"). This flag allows you to
+  use a different file.
+)";
+
+const char kFailOnUnusedArgs[] = "fail-on-unused-args";
+const char kFailOnUnusedArgs_HelpShort[] =
+    "--fail-on-unused-args: Treat unused build args as fatal errors.";
+const char kFailOnUnusedArgs_Help[] =
+    R"(--fail-on-unused-args: Treat unused build args as fatal errors.
+
+  If you set a value in a build's "gn args" and never use it in the build (in
+  a declare_args() block), GN will normally print an error but not fail the
+  build.
+
+  In many cases engineers would use build args to enable or disable features
+  that would sometimes get removed. It would by annoying to block work for
+  typically benign problems. In Chrome in particular, flags might be configured
+  for build bots in a separate infrastructure repository, or a declare_args
+  block might be changed in a third party repository. Treating these errors as
+  blocking forced complex multi- way patches to land what would otherwise be
+  simple changes.
+
+  In some cases, such concerns are not as important, and a mismatch in build
+  flags between the invoker of the build and the build files represents a
+  critical mismatch that should be immediately fixed. Such users can set this
+  flag to force GN to fail in that case.
+)";
+
+const char kMarkdown[] = "markdown";
+const char kMarkdown_HelpShort[] =
+    "--markdown: Write help output in the Markdown format.";
+const char kMarkdown_Help[] =
+    "--markdown: Write help output in the Markdown format.\n";
+
+const char kNoColor[] = "nocolor";
+const char kNoColor_HelpShort[] = "--nocolor: Force non-colored output.";
+const char kNoColor_Help[] = COLOR_HELP_LONG;
+
+const char kNinjaExecutable[] = "ninja-executable";
+const char kNinjaExecutable_HelpShort[] =
+    "--ninja-executable: Set the Ninja executable.";
+const char kNinjaExecutable_Help[] =
+    R"(--ninja-executable: Set the Ninja executable.
+
+  When set specifies the Ninja executable that will be used to perform some
+  post-processing on the generated files for more consistent builds.
+)";
+
+const char kScriptExecutable[] = "script-executable";
+const char kScriptExecutable_HelpShort[] =
+    "--script-executable: Set the executable used to execute scripts.";
+const char kScriptExecutable_Help[] =
+    R"(--script-executable: Set the executable used to execute scripts.
+
+  Path to specific Python executable or other interpreter to use in
+  action targets and exec_script calls. By default GN searches the
+  PATH for Python to execute these scripts.
+
+  If set to the empty string, the path specified in action targets
+  and exec_script calls will be executed directly.
+)";
+
+const char kMetaDataKeys[] = "data";
+const char kMetaDataKeys_HelpShort[] =
+    "--data: list of data keys to concatenate when collecting metadata.";
+const char kMetaDataKeys_Help[] =
+    R"(--data: list of data keys to concatenate when collecting metadata.
+
+  Data keys identify which variables in the given targets' `metadata`
+  scopes should be collected. At least one data key must be specified.
+)";
+
+const char kMetaWalkKeys[] = "walk";
+const char kMetaWalkKeys_HelpShort[] =
+    "--walk: list of walk keys to traverse when collecting metadata.";
+const char kMetaWalkKeys_Help[] =
+    R"(--walk: list of walk keys to traverse when collecting metadata.
+
+  Walk keys identify which variables in the given targets' `metadata`
+  scopes contain the list of dependencies to walk next. Absence of any
+  walk keys indicates that all deps and data_deps should be walked.
+)";
+
+const char kMetaRebaseFiles[] = "rebase-files";
+const char kMetaRebaseFiles_HelpShort[] =
+    "--rebase-files (boolean): whether to rebase the paths of the collected "
+    "metadata.";
+const char kMetaRebaseFiles_Help[] =
+    R"(--rebase-files: whether to rebase the paths of the collected metadata.
+
+  This flag indicates whether or not to rebase the collected results onto their
+  declaring source directory path. Note that this requires the data key(s) to
+  contain only lists of strings, which will be interpreted as file names.
+)";
+
+const char kQuiet[] = "q";
+const char kQuiet_HelpShort[] =
+    "-q: Quiet mode. Don't print output on success.";
+const char kQuiet_Help[] =
+    R"(-q: Quiet mode. Don't print output on success.
+
+  This is useful when running as a part of another script.
+)";
+
+const char kRoot[] = "root";
+const char kRoot_HelpShort[] = "--root: Explicitly specify source root.";
+const char kRoot_Help[] =
+    R"(--root: Explicitly specify source root.
+
+  Normally GN will look up in the directory tree from the current directory to
+  find a ".gn" file. The source root directory specifies the meaning of "//"
+  beginning with paths, and the BUILD.gn file in that directory will be the
+  first thing loaded.
+
+  Specifying --root allows GN to do builds in a specific directory regardless
+  of the current directory.
+
+Examples
+
+  gn gen //out/Default --root=/home/baracko/src
+
+  gn desc //out/Default --root="C:\Users\BObama\My Documents\foo"
+)";
+
+const char kRootTarget[] = "root-target";
+const char kRootTarget_HelpShort[] =
+    "--root-target: Override the root target.";
+const char kRootTarget_Help[] =
+    R"(--root-target: Override the root target.
+
+  The root target is the target initially loaded to begin population of the
+  build graph. It defaults to "//:" which normally causes the "//BUILD.gn" file
+  to be loaded. It can be specified in the .gn file via the "root" variable (see
+  "gn help dotfile").
+
+  If specified, the value of this switch will be take precedence over the value
+  in ".gn". The target name (after the colon) is ignored, only the directory
+  name is required. Relative paths will be resolved relative to the current "//"
+  directory.
+
+  Specifying a different initial BUILD.gn file does not change the meaning of
+  the source root (the "//" directory) which can be independently set via the
+  --root switch. It also does not prevent the build file located at "//BUILD.gn"
+  from being loaded if a target in the build references that directory.
+
+  One use-case of this feature is to load a different set of initial targets
+  from project that uses GN without modifying any files.
+
+Examples
+
+  gn gen //out/Default --root-target="//third_party/icu"
+
+  gn gen //out/Default --root-target="//third_party/grpc"
+)";
+
+const char kRuntimeDepsListFile[] = "runtime-deps-list-file";
+const char kRuntimeDepsListFile_HelpShort[] =
+    "--runtime-deps-list-file: Save runtime dependencies for targets in file.";
+const char kRuntimeDepsListFile_Help[] =
+    R"(--runtime-deps-list-file: Save runtime dependencies for targets in file.
+
+  --runtime-deps-list-file=<filename>
+
+  Where <filename> is a text file consisting of the labels, one per line, of
+  the targets for which runtime dependencies are desired.
+
+  See "gn help runtime_deps" for a description of how runtime dependencies are
+  computed.
+
+Runtime deps output file
+
+  For each target requested, GN will write a separate runtime dependency file.
+  The runtime dependency file will be in the output directory alongside the
+  output file of the target, with a ".runtime_deps" extension. For example, if
+  the target "//foo:bar" is listed in the input file, and that target produces
+  an output file "bar.so", GN will create a file "bar.so.runtime_deps" in the
+  build directory.
+
+  If a source set, action, copy, or group is listed, the runtime deps file will
+  correspond to the .stamp file corresponding to that target. This is probably
+  not useful; the use-case for this feature is generally executable targets.
+
+  The runtime dependency file will list one file per line, with no escaping.
+  The files will be relative to the root_build_dir. The first line of the file
+  will be the main output file of the target itself (in the above example,
+  "bar.so").
+)";
+
+const char kThreads[] = "threads";
+const char kThreads_HelpShort[] =
+    "--threads: Specify number of worker threads.";
+const char kThreads_Help[] =
+    R"(--threads: Specify number of worker threads.
+
+  GN runs many threads to load and run build files. This can make debugging
+  challenging. Or you may want to experiment with different values to see how
+  it affects performance.
+
+  The parameter is the number of worker threads. This does not count the main
+  thread (so there are always at least two).
+
+Examples
+
+  gen gen out/Default --threads=1
+)";
+
+const char kTime[] = "time";
+const char kTime_HelpShort[] =
+    "--time: Outputs a summary of how long everything took.";
+const char kTime_Help[] =
+    R"(--time: Outputs a summary of how long everything took.
+
+  Hopefully self-explanatory.
+
+Examples
+
+  gn gen out/Default --time
+)";
+
+const char kTracelog[] = "tracelog";
+const char kTracelog_HelpShort[] =
+    "--tracelog: Writes a Chrome-compatible trace log to the given file.";
+const char kTracelog_Help[] =
+    R"(--tracelog: Writes a Chrome-compatible trace log to the given file.
+
+  The trace log will show file loads, executions, scripts, and writes. This
+  allows performance analysis of the generation step.
+
+  To view the trace, open Chrome and navigate to "chrome://tracing/", then
+  press "Load" and specify the file you passed to this parameter.
+
+Examples
+
+  gn gen out/Default --tracelog=mytrace.trace
+)";
+
+const char kVerbose[] = "v";
+const char kVerbose_HelpShort[] = "-v: Verbose logging.";
+const char kVerbose_Help[] =
+    R"(-v: Verbose logging.
+
+  This will spew logging events to the console for debugging issues.
+
+  Good luck!
+)";
+
+const char kVersion[] = "version";
+const char kVersion_HelpShort[] =
+    "--version: Prints the GN version number and exits.";
+// It's impossible to see this since gn_main prints the version and exits
+// immediately if this switch is used.
+const char kVersion_Help[] = "";
+
+const char kDefaultToolchain[] = "default-toolchain";
+
+const char kRegeneration[] = "regeneration";
+// -----------------------------------------------------------------------------
+
+SwitchInfo::SwitchInfo() : short_help(""), long_help("") {}
+
+SwitchInfo::SwitchInfo(const char* short_help, const char* long_help)
+    : short_help(short_help), long_help(long_help) {}
+
+#define INSERT_VARIABLE(var) \
+  info_map[k##var] = SwitchInfo(k##var##_HelpShort, k##var##_Help);
+
+const SwitchInfoMap& GetSwitches() {
+  static SwitchInfoMap info_map;
+  if (info_map.empty()) {
+    INSERT_VARIABLE(Args)
+    INSERT_VARIABLE(Color)
+    INSERT_VARIABLE(Dotfile)
+    INSERT_VARIABLE(FailOnUnusedArgs)
+    INSERT_VARIABLE(Markdown)
+    INSERT_VARIABLE(NinjaExecutable)
+    INSERT_VARIABLE(NoColor)
+    INSERT_VARIABLE(Root)
+    INSERT_VARIABLE(RootTarget)
+    INSERT_VARIABLE(Quiet)
+    INSERT_VARIABLE(RuntimeDepsListFile)
+    INSERT_VARIABLE(ScriptExecutable)
+    INSERT_VARIABLE(Threads)
+    INSERT_VARIABLE(Time)
+    INSERT_VARIABLE(Tracelog)
+    INSERT_VARIABLE(Verbose)
+    INSERT_VARIABLE(Version)
+  }
+  return info_map;
+}
+
+#undef INSERT_VARIABLE
+
+}  // namespace switches
diff --git a/src/gn/switches.h b/src/gn/switches.h
new file mode 100644 (file)
index 0000000..2382bb7
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SWITCHES_H_
+#define TOOLS_GN_SWITCHES_H_
+
+#include <map>
+#include <string_view>
+
+namespace switches {
+
+struct SwitchInfo {
+  SwitchInfo();
+  SwitchInfo(const char* short_help, const char* long_help);
+
+  const char* short_help;
+  const char* long_help;
+};
+
+using SwitchInfoMap = std::map<std::string_view, SwitchInfo>;
+
+// Returns the mapping of all global switches.
+const SwitchInfoMap& GetSwitches();
+
+// This file contains global switches. If a command takes specific ones only
+// to that command, just put them in that command's .cc file.
+
+extern const char kArgs[];
+extern const char kArgs_HelpShort[];
+extern const char kArgs_Help[];
+
+extern const char kColor[];
+extern const char kColor_HelpShort[];
+extern const char kColor_Help[];
+
+extern const char kDotfile[];
+extern const char kDotfile_HelpShort[];
+extern const char kDotfile_Help[];
+
+extern const char kFailOnUnusedArgs[];
+extern const char kFailOnUnusedArgs_HelpShort[];
+extern const char kFailOnUnusedArgs_Help[];
+
+extern const char kMarkdown[];
+extern const char kMarkdown_HelpShort[];
+extern const char kMarkdown_Help[];
+
+extern const char kMetaDataKeys[];
+extern const char kMetaDataKeys_HelpShort[];
+extern const char kMetaDataKeys_Help[];
+
+extern const char kMetaWalkKeys[];
+extern const char kMetaWalkKeys_HelpShort[];
+extern const char kMetaWalkKeys_Help[];
+
+extern const char kMetaRebaseFiles[];
+extern const char kMetaRebaseFiles_HelpShort[];
+extern const char kMetaRebaseFiles_Help[];
+
+extern const char kNinjaExecutable[];
+extern const char kNinjaExecutable_HelpShort[];
+extern const char kNinjaExecutable_Help[];
+
+extern const char kNoColor[];
+extern const char kNoColor_HelpShort[];
+extern const char kNoColor_Help[];
+
+extern const char kScriptExecutable[];
+extern const char kScriptExecutable_HelpShort[];
+extern const char kScriptExecutable_Help[];
+
+extern const char kQuiet[];
+extern const char kQuiet_HelpShort[];
+extern const char kQuiet_Help[];
+
+extern const char kRoot[];
+extern const char kRoot_HelpShort[];
+extern const char kRoot_Help[];
+
+extern const char kRootTarget[];
+extern const char kRootTarget_HelpShort[];
+extern const char kRootTarget_Help[];
+
+extern const char kRuntimeDepsListFile[];
+extern const char kRuntimeDepsListFile_HelpShort[];
+extern const char kRuntimeDepsListFile_Help[];
+
+extern const char kThreads[];
+extern const char kThreads_HelpShort[];
+extern const char kThreads_Help[];
+
+extern const char kTime[];
+extern const char kTime_HelpShort[];
+extern const char kTime_Help[];
+
+extern const char kTracelog[];
+extern const char kTracelog_HelpShort[];
+extern const char kTracelog_Help[];
+
+extern const char kVerbose[];
+extern const char kVerbose_HelpShort[];
+extern const char kVerbose_Help[];
+
+extern const char kVersion[];
+extern const char kVersion_HelpShort[];
+extern const char kVersion_Help[];
+
+// This switch is used by several commands. It is here so it can be shared,
+// but it's documented in the individual commands it applies to rather than
+// globally.
+extern const char kDefaultToolchain[];
+#define DEFAULT_TOOLCHAIN_SWITCH_HELP                                         \
+  "  --default-toolchain\n"                                                   \
+  "      Normally wildcard targets are matched in all toolchains. This\n"     \
+  "      switch makes wildcard labels with no explicit toolchain reference\n" \
+  "      only match targets in the default toolchain.\n"                      \
+  "\n"                                                                        \
+  "      Non-wildcard inputs with no explicit toolchain specification will\n" \
+  "      always match only a target in the default toolchain if one exists.\n"
+
+// This switch is used to signal to the gen command that it is being invoked on
+// a regeneration step. Ie, ninja has realized that build.ninja needs to be
+// generated again and has invoked gn gen. There is no help associated with it
+// because users should not be setting this switch. It is located in this file
+// so it can be shared between command_gen and ninja_build_writer.
+extern const char kRegeneration[];
+
+}  // namespace switches
+
+#endif  // TOOLS_GN_SWITCHES_H_
diff --git a/src/gn/target.cc b/src/gn/target.cc
new file mode 100644 (file)
index 0000000..cee2ed2
--- /dev/null
@@ -0,0 +1,1170 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/target.h"
+
+#include <stddef.h>
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "gn/c_tool.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/scheduler.h"
+#include "gn/substitution_writer.h"
+#include "gn/tool.h"
+#include "gn/toolchain.h"
+#include "gn/trace.h"
+
+namespace {
+
+using ConfigSet = std::set<const Config*>;
+
+// Merges the public configs from the given target to the given config list.
+void MergePublicConfigsFrom(const Target* from_target,
+                            UniqueVector<LabelConfigPair>* dest) {
+  const UniqueVector<LabelConfigPair>& pub = from_target->public_configs();
+  dest->Append(pub.begin(), pub.end());
+}
+
+// Like MergePublicConfigsFrom above except does the "all dependent" ones. This
+// additionally adds all configs to the all_dependent_configs_ of the dest
+// target given in *all_dest.
+void MergeAllDependentConfigsFrom(const Target* from_target,
+                                  UniqueVector<LabelConfigPair>* dest,
+                                  UniqueVector<LabelConfigPair>* all_dest) {
+  for (const auto& pair : from_target->all_dependent_configs()) {
+    all_dest->push_back(pair);
+    dest->push_back(pair);
+  }
+}
+
+Err MakeTestOnlyError(const Target* from, const Target* to) {
+  return Err(
+      from->defined_from(), "Test-only dependency not allowed.",
+      from->label().GetUserVisibleName(false) +
+          "\n"
+          "which is NOT marked testonly can't depend on\n" +
+          to->label().GetUserVisibleName(false) +
+          "\n"
+          "which is marked testonly. Only targets with \"testonly = true\"\n"
+          "can depend on other test-only targets.\n"
+          "\n"
+          "Either mark it test-only or don't do this dependency.");
+}
+
+// Set check_private_deps to true for the first invocation since a target
+// can see all of its dependencies. For recursive invocations this will be set
+// to false to follow only public dependency paths.
+//
+// Pass a pointer to an empty set for the first invocation. This will be used
+// to avoid duplicate checking.
+//
+// Checking of object files is optional because it is much slower. This allows
+// us to check targets for normal outputs, and then as a second pass check
+// object files (since we know it will be an error otherwise). This allows
+// us to avoid computing all object file names in the common case.
+bool EnsureFileIsGeneratedByDependency(const Target* target,
+                                       const OutputFile& file,
+                                       bool check_private_deps,
+                                       bool consider_object_files,
+                                       bool check_data_deps,
+                                       std::set<const Target*>* seen_targets) {
+  if (seen_targets->find(target) != seen_targets->end())
+    return false;  // Already checked this one and it's not found.
+  seen_targets->insert(target);
+
+  // Assume that we have relatively few generated inputs so brute-force
+  // searching here is OK. If this becomes a bottleneck, consider storing
+  // computed_outputs as a hash set.
+  for (const OutputFile& cur : target->computed_outputs()) {
+    if (file == cur)
+      return true;
+  }
+
+  if (file == target->write_runtime_deps_output())
+    return true;
+
+  // Check binary target intermediate files if requested.
+  if (consider_object_files && target->IsBinary()) {
+    std::vector<OutputFile> source_outputs;
+    for (const SourceFile& source : target->sources()) {
+      const char* tool_name;
+      if (!target->GetOutputFilesForSource(source, &tool_name, &source_outputs))
+        continue;
+      if (base::ContainsValue(source_outputs, file))
+        return true;
+    }
+  }
+
+  if (check_data_deps) {
+    check_data_deps = false;  // Consider only direct data_deps.
+    for (const auto& pair : target->data_deps()) {
+      if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                            consider_object_files,
+                                            check_data_deps, seen_targets))
+        return true;  // Found a path.
+    }
+  }
+
+  // Check all public dependencies (don't do data ones since those are
+  // runtime-only).
+  for (const auto& pair : target->public_deps()) {
+    if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                          consider_object_files,
+                                          check_data_deps, seen_targets))
+      return true;  // Found a path.
+  }
+
+  // Only check private deps if requested.
+  if (check_private_deps) {
+    for (const auto& pair : target->private_deps()) {
+      if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                            consider_object_files,
+                                            check_data_deps, seen_targets))
+        return true;  // Found a path.
+    }
+    if (target->output_type() == Target::CREATE_BUNDLE) {
+      for (auto* dep : target->bundle_data().bundle_deps()) {
+        if (EnsureFileIsGeneratedByDependency(dep, file, false,
+                                              consider_object_files,
+                                              check_data_deps, seen_targets))
+          return true;  // Found a path.
+      }
+    }
+  }
+  return false;
+}
+
+// check_this indicates if the given target should be matched against the
+// patterns. It should be set to false for the first call since assert_no_deps
+// shouldn't match the target itself.
+//
+// visited should point to an empty set, this will be used to prevent
+// multiple visits.
+//
+// *failure_path_str will be filled with a string describing the path of the
+// dependency failure, and failure_pattern will indicate the pattern in
+// assert_no that matched the target.
+//
+// Returns true if everything is OK. failure_path_str and failure_pattern_index
+// will be unchanged in this case.
+bool RecursiveCheckAssertNoDeps(const Target* target,
+                                bool check_this,
+                                const std::vector<LabelPattern>& assert_no,
+                                std::set<const Target*>* visited,
+                                std::string* failure_path_str,
+                                const LabelPattern** failure_pattern) {
+  static const char kIndentPath[] = "  ";
+
+  if (visited->find(target) != visited->end())
+    return true;  // Already checked this target.
+  visited->insert(target);
+
+  if (check_this) {
+    // Check this target against the given list of patterns.
+    for (const LabelPattern& pattern : assert_no) {
+      if (pattern.Matches(target->label())) {
+        // Found a match.
+        *failure_pattern = &pattern;
+        *failure_path_str =
+            kIndentPath + target->label().GetUserVisibleName(false);
+        return false;
+      }
+    }
+  }
+
+  // Recursively check dependencies.
+  for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
+    if (pair.ptr->output_type() == Target::EXECUTABLE)
+      continue;
+    if (!RecursiveCheckAssertNoDeps(pair.ptr, true, assert_no, visited,
+                                    failure_path_str, failure_pattern)) {
+      // To reconstruct the path, prepend the current target to the error.
+      std::string prepend_path =
+          kIndentPath + target->label().GetUserVisibleName(false) + " ->\n";
+      failure_path_str->insert(0, prepend_path);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+const char kExecution_Help[] =
+    R"(Build graph and execution overview
+
+Overall build flow
+
+  1. Look for ".gn" file (see "gn help dotfile") in the current directory and
+     walk up the directory tree until one is found. Set this directory to be
+     the "source root" and interpret this file to find the name of the build
+     config file.
+
+  2. Execute the build config file identified by .gn to set up the global
+     variables and default toolchain name. Any arguments, variables, defaults,
+     etc. set up in this file will be visible to all files in the build.
+
+  3. Load the //BUILD.gn (in the source root directory).
+
+  4. Recursively evaluate rules and load BUILD.gn in other directories as
+     necessary to resolve dependencies. If a BUILD file isn't found in the
+     specified location, GN will look in the corresponding location inside
+     the secondary_source defined in the dotfile (see "gn help dotfile").
+
+  5. When a target's dependencies are resolved, write out the `.ninja`
+     file to disk.
+
+  6. When all targets are resolved, write out the root build.ninja file.
+
+  Note that the BUILD.gn file name may be modulated by .gn arguments such as
+  build_file_extension.
+
+Executing target definitions and templates
+
+  Build files are loaded in parallel. This means it is impossible to
+  interrogate a target from GN code for any information not derivable from its
+  label (see "gn help label"). The exception is the get_target_outputs()
+  function which requires the target being interrogated to have been defined
+  previously in the same file.
+
+  Targets are declared by their type and given a name:
+
+    static_library("my_static_library") {
+      ... target parameter definitions ...
+    }
+
+  There is also a generic "target" function for programmatically defined types
+  (see "gn help target"). You can define new types using templates (see "gn
+  help template"). A template defines some custom code that expands to one or
+  more other targets.
+
+  Before executing the code inside the target's { }, the target defaults are
+  applied (see "gn help set_defaults"). It will inject implicit variable
+  definitions that can be overridden by the target code as necessary. Typically
+  this mechanism is used to inject a default set of configs that define the
+  global compiler and linker flags.
+
+Which targets are built
+
+  All targets encountered in the default toolchain (see "gn help toolchain")
+  will have build rules generated for them, even if no other targets reference
+  them. Their dependencies must resolve and they will be added to the implicit
+  "all" rule (see "gn help ninja_rules").
+
+  Targets in non-default toolchains will only be generated when they are
+  required (directly or transitively) to build a target in the default
+  toolchain.
+
+  See also "gn help ninja_rules".
+
+Dependencies
+
+  The only difference between "public_deps" and "deps" except for pushing
+  configs around the build tree and allowing includes for the purposes of "gn
+  check".
+
+  A target's "data_deps" are guaranteed to be built whenever the target is
+  built, but the ordering is not defined. The meaning of this is dependencies
+  required at runtime. Currently data deps will be complete before the target
+  is linked, but this is not semantically guaranteed and this is undesirable
+  from a build performance perspective. Since we hope to change this in the
+  future, do not rely on this behavior.
+)";
+
+Target::Target(const Settings* settings,
+               const Label& label,
+               const SourceFileSet& build_dependency_files)
+    : Item(settings, label, build_dependency_files) {}
+
+Target::~Target() = default;
+
+// static
+const char* Target::GetStringForOutputType(OutputType type) {
+  switch (type) {
+    case UNKNOWN:
+      return "unknown";
+    case GROUP:
+      return functions::kGroup;
+    case EXECUTABLE:
+      return functions::kExecutable;
+    case LOADABLE_MODULE:
+      return functions::kLoadableModule;
+    case SHARED_LIBRARY:
+      return functions::kSharedLibrary;
+    case STATIC_LIBRARY:
+      return functions::kStaticLibrary;
+    case SOURCE_SET:
+      return functions::kSourceSet;
+    case COPY_FILES:
+      return functions::kCopy;
+    case ACTION:
+      return functions::kAction;
+    case ACTION_FOREACH:
+      return functions::kActionForEach;
+    case BUNDLE_DATA:
+      return functions::kBundleData;
+    case CREATE_BUNDLE:
+      return functions::kCreateBundle;
+    case GENERATED_FILE:
+      return functions::kGeneratedFile;
+    case RUST_LIBRARY:
+      return functions::kRustLibrary;
+    case RUST_PROC_MACRO:
+      return functions::kRustProcMacro;
+    default:
+      return "";
+  }
+}
+
+Target* Target::AsTarget() {
+  return this;
+}
+
+const Target* Target::AsTarget() const {
+  return this;
+}
+
+bool Target::OnResolved(Err* err) {
+  DCHECK(output_type_ != UNKNOWN);
+  DCHECK(toolchain_) << "Toolchain should have been set before resolving.";
+
+  ScopedTrace trace(TraceItem::TRACE_ON_RESOLVED, label());
+  trace.SetToolchain(settings()->toolchain_label());
+
+  // Check visibility for just this target's own configs, before dependents are
+  // added.
+  if (!CheckConfigVisibility(err))
+    return false;
+
+  // Copy this target's own dependent and public configs to the list of configs
+  // applying to it.
+  configs_.Append(all_dependent_configs_.begin(), all_dependent_configs_.end());
+  MergePublicConfigsFrom(this, &configs_);
+
+  // Copy public configs from all dependencies into the list of configs
+  // applying to this target (configs_).
+  PullDependentTargetConfigs();
+
+  // Copies public dependencies' public configs to this target's public
+  // configs. These configs have already been applied to this target by
+  // PullDependentTargetConfigs above, along with the public configs from
+  // private deps. This step re-exports them as public configs for targets that
+  // depend on this one.
+  for (const auto& dep : public_deps_) {
+    if (dep.ptr->toolchain() == toolchain() ||
+        dep.ptr->toolchain()->propagates_configs())
+      public_configs_.Append(dep.ptr->public_configs().begin(),
+                             dep.ptr->public_configs().end());
+  }
+
+  // Copy our own libs and lib_dirs to the final set. This will be from our
+  // target and all of our configs. We do this specially since these must be
+  // inherited through the dependency tree (other flags don't work this way).
+  //
+  // This needs to happen after we pull dependent target configs for the
+  // public config's libs to be included here. And it needs to happen
+  // before pulling the dependent target libs so the libs are in the correct
+  // order (local ones first, then the dependency's).
+  for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
+    const ConfigValues& cur = iter.cur();
+    all_lib_dirs_.append(cur.lib_dirs().begin(), cur.lib_dirs().end());
+    all_libs_.append(cur.libs().begin(), cur.libs().end());
+
+    all_framework_dirs_.append(cur.framework_dirs().begin(),
+                               cur.framework_dirs().end());
+    all_frameworks_.append(cur.frameworks().begin(), cur.frameworks().end());
+    all_weak_frameworks_.append(cur.weak_frameworks().begin(),
+                                cur.weak_frameworks().end());
+  }
+
+  PullRecursiveBundleData();
+  PullDependentTargetLibs();
+  PullRecursiveHardDeps();
+  if (!ResolvePrecompiledHeaders(err))
+    return false;
+
+  if (!FillOutputFiles(err))
+    return false;
+
+  if (!swift_values_.OnTargetResolved(this, err))
+    return false;
+
+  if (!CheckSourceSetLanguages(err))
+    return false;
+  if (!CheckVisibility(err))
+    return false;
+  if (!CheckTestonly(err))
+    return false;
+  if (!CheckAssertNoDeps(err))
+    return false;
+  CheckSourcesGenerated();
+
+  if (!write_runtime_deps_output_.value().empty())
+    g_scheduler->AddWriteRuntimeDepsTarget(this);
+
+  if (output_type_ == GENERATED_FILE) {
+    DCHECK(!computed_outputs_.empty());
+    g_scheduler->AddGeneratedFile(
+        computed_outputs_[0].AsSourceFile(settings()->build_settings()));
+  }
+
+  return true;
+}
+
+bool Target::IsBinary() const {
+  return output_type_ == EXECUTABLE || output_type_ == SHARED_LIBRARY ||
+         output_type_ == LOADABLE_MODULE || output_type_ == STATIC_LIBRARY ||
+         output_type_ == SOURCE_SET || output_type_ == RUST_LIBRARY ||
+         output_type_ == RUST_PROC_MACRO;
+}
+
+bool Target::IsLinkable() const {
+  return output_type_ == STATIC_LIBRARY || output_type_ == SHARED_LIBRARY ||
+         output_type_ == RUST_LIBRARY || output_type_ == RUST_PROC_MACRO;
+}
+
+bool Target::IsFinal() const {
+  return output_type_ == EXECUTABLE || output_type_ == SHARED_LIBRARY ||
+         output_type_ == LOADABLE_MODULE || output_type_ == ACTION ||
+         output_type_ == ACTION_FOREACH || output_type_ == COPY_FILES ||
+         output_type_ == CREATE_BUNDLE || output_type_ == RUST_PROC_MACRO ||
+         (output_type_ == STATIC_LIBRARY && complete_static_lib_);
+}
+
+bool Target::IsDataOnly() const {
+  // BUNDLE_DATA exists only to declare inputs to subsequent CREATE_BUNDLE
+  // targets. Changing only contents of the bundle data target should not cause
+  // a binary to be re-linked. It should affect only the CREATE_BUNDLE steps
+  // instead. As a result, normal targets should treat this as a data
+  // dependency.
+  return output_type_ == BUNDLE_DATA;
+}
+
+DepsIteratorRange Target::GetDeps(DepsIterationType type) const {
+  if (type == DEPS_LINKED) {
+    return DepsIteratorRange(
+        DepsIterator(&public_deps_, &private_deps_, nullptr));
+  }
+  // All deps.
+  return DepsIteratorRange(
+      DepsIterator(&public_deps_, &private_deps_, &data_deps_));
+}
+
+std::string Target::GetComputedOutputName() const {
+  DCHECK(toolchain_)
+      << "Toolchain must be specified before getting the computed output name.";
+
+  const std::string& name =
+      output_name_.empty() ? label().name() : output_name_;
+
+  std::string result;
+  const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+  if (tool) {
+    // Only add the prefix if the name doesn't already have it and it's not
+    // being overridden.
+    if (!output_prefix_override_ &&
+        !base::StartsWith(name, tool->output_prefix(),
+                          base::CompareCase::SENSITIVE))
+      result = tool->output_prefix();
+  }
+  result.append(name);
+  return result;
+}
+
+bool Target::SetToolchain(const Toolchain* toolchain, Err* err) {
+  DCHECK(!toolchain_);
+  DCHECK_NE(UNKNOWN, output_type_);
+  toolchain_ = toolchain;
+
+  const Tool* tool = toolchain->GetToolForTargetFinalOutput(this);
+  if (tool)
+    return true;
+
+  // Tool not specified for this target type.
+  if (err) {
+    *err =
+        Err(defined_from(), "This target uses an undefined tool.",
+            base::StringPrintf(
+                "The target %s\n"
+                "of type \"%s\"\n"
+                "uses toolchain %s\n"
+                "which doesn't have the tool \"%s\" defined.\n\n"
+                "Alas, I can not continue.",
+                label().GetUserVisibleName(false).c_str(),
+                GetStringForOutputType(output_type_),
+                label().GetToolchainLabel().GetUserVisibleName(false).c_str(),
+                Tool::GetToolTypeForTargetFinalOutput(this)));
+  }
+  return false;
+}
+
+bool Target::GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
+                                     bool build_complete,
+                                     std::vector<SourceFile>* outputs,
+                                     Err* err) const {
+  const static char kBuildIncompleteMsg[] =
+      "This target is a binary target which can't be queried for its "
+      "outputs\nduring the build. It will work for action, action_foreach, "
+      "generated_file,\nand copy targets.";
+
+  outputs->clear();
+
+  std::vector<SourceFile> files;
+  if (output_type() == Target::ACTION || output_type() == Target::COPY_FILES ||
+      output_type() == Target::ACTION_FOREACH ||
+      output_type() == Target::GENERATED_FILE) {
+    action_values().GetOutputsAsSourceFiles(this, outputs);
+  } else if (output_type() == Target::CREATE_BUNDLE) {
+    if (!bundle_data().GetOutputsAsSourceFiles(settings(), this, outputs, err))
+      return false;
+  } else if (IsBinary() && output_type() != Target::SOURCE_SET) {
+    // Binary target with normal outputs (source sets have stamp outputs like
+    // groups).
+    DCHECK(IsBinary()) << static_cast<int>(output_type());
+    if (!build_complete) {
+      // Can't access the toolchain for a target before the build is complete.
+      // Otherwise it will race with loading and setting the toolchain
+      // definition.
+      *err = Err(loc_for_error, kBuildIncompleteMsg);
+      return false;
+    }
+
+    const Tool* tool = toolchain()->GetToolForTargetFinalOutput(this);
+
+    std::vector<OutputFile> output_files;
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        this, tool, tool->outputs(), &output_files);
+    for (const OutputFile& output_file : output_files) {
+      outputs->push_back(
+          output_file.AsSourceFile(settings()->build_settings()));
+    }
+  } else {
+    // Everything else (like a group or bundle_data) has a stamp output. The
+    // dependency output file should have computed what this is. This won't be
+    // valid unless the build is complete.
+    if (!build_complete) {
+      *err = Err(loc_for_error, kBuildIncompleteMsg);
+      return false;
+    }
+    outputs->push_back(
+        dependency_output_file().AsSourceFile(settings()->build_settings()));
+  }
+  return true;
+}
+
+bool Target::GetOutputFilesForSource(const SourceFile& source,
+                                     const char** computed_tool_type,
+                                     std::vector<OutputFile>* outputs) const {
+  DCHECK(toolchain());  // Should be resolved before calling.
+
+  outputs->clear();
+  *computed_tool_type = Tool::kToolNone;
+
+  if (output_type() == Target::COPY_FILES ||
+      output_type() == Target::ACTION_FOREACH) {
+    // These target types apply the output pattern to the input.
+    std::vector<SourceFile> output_files;
+    SubstitutionWriter::ApplyListToSourceAsOutputFile(
+        this, settings(), action_values().outputs(), source, outputs);
+  } else if (!IsBinary()) {
+    // All other non-binary target types just return the target outputs. We
+    // don't know if the build is complete and it doesn't matter for non-binary
+    // targets, so just assume it's not and pass "false".
+    std::vector<SourceFile> outputs_as_source_files;
+    Err err;  // We can ignore the error and return empty for failure.
+    GetOutputsAsSourceFiles(LocationRange(), false, &outputs_as_source_files,
+                            &err);
+
+    // Convert to output files.
+    for (const auto& cur : outputs_as_source_files)
+      outputs->emplace_back(OutputFile(settings()->build_settings(), cur));
+  } else {
+    // All binary targets do a tool lookup.
+    DCHECK(IsBinary());
+
+    SourceFile::Type file_type = source.type();
+    if (file_type == SourceFile::SOURCE_UNKNOWN)
+      return false;
+    if (file_type == SourceFile::SOURCE_O) {
+      // Object files just get passed to the output and not compiled.
+      outputs->emplace_back(OutputFile(settings()->build_settings(), source));
+      return true;
+    }
+
+    // Rust generates on a module level, not source.
+    if (file_type == SourceFile::SOURCE_RS)
+      return false;
+
+    *computed_tool_type = Tool::GetToolTypeForSourceType(file_type);
+    if (*computed_tool_type == Tool::kToolNone)
+      return false;  // No tool for this file (it's a header file or something).
+    const Tool* tool = toolchain_->GetTool(*computed_tool_type);
+    if (!tool)
+      return false;  // Tool does not apply for this toolchain.file.
+
+    // Swift may generate on a module or source level.
+    if (file_type == SourceFile::SOURCE_SWIFT) {
+      if (tool->partial_outputs().list().empty())
+        return false;
+    }
+
+    const SubstitutionList& substitution_list =
+        file_type == SourceFile::SOURCE_SWIFT ? tool->partial_outputs()
+                                              : tool->outputs();
+
+    // Figure out what output(s) this compiler produces.
+    SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+        this, source, substitution_list, outputs);
+  }
+  return !outputs->empty();
+}
+
+void Target::PullDependentTargetConfigs() {
+  for (const auto& pair : GetDeps(DEPS_LINKED)) {
+    if (pair.ptr->toolchain() == toolchain() ||
+        pair.ptr->toolchain()->propagates_configs())
+      MergeAllDependentConfigsFrom(pair.ptr, &configs_,
+                                   &all_dependent_configs_);
+  }
+  for (const auto& pair : GetDeps(DEPS_LINKED)) {
+    if (pair.ptr->toolchain() == toolchain() ||
+        pair.ptr->toolchain()->propagates_configs())
+      MergePublicConfigsFrom(pair.ptr, &configs_);
+  }
+}
+
+void Target::PullDependentTargetLibsFrom(const Target* dep, bool is_public) {
+  // Direct dependent libraries.
+  if (dep->output_type() == STATIC_LIBRARY ||
+      dep->output_type() == SHARED_LIBRARY) {
+    inherited_libraries_.Append(dep, is_public);
+    rust_values().transitive_libs().Append(dep, is_public);
+  }
+
+  if (dep->output_type() == RUST_LIBRARY ||
+      dep->output_type() == RUST_PROC_MACRO ||
+      dep->output_type() == SOURCE_SET ||
+      (dep->output_type() == CREATE_BUNDLE &&
+       dep->bundle_data().is_framework())) {
+    inherited_libraries_.Append(dep, is_public);
+  }
+
+  if (dep->output_type() == RUST_LIBRARY) {
+    rust_values().transitive_libs().Append(dep, is_public);
+    rust_values().transitive_libs().AppendInherited(
+        dep->rust_values().transitive_libs(), is_public);
+
+    // If there is a transitive dependency that is not a rust library, place it
+    // in the normal location
+    for (const auto& inherited :
+         rust_values().transitive_libs().GetOrderedAndPublicFlag()) {
+      if (!(inherited.first->output_type() == RUST_LIBRARY ||
+            inherited.first->output_type() == RUST_PROC_MACRO)) {
+        inherited_libraries_.Append(inherited.first,
+                                    is_public && inherited.second);
+      }
+    }
+  } else if (dep->output_type() == RUST_PROC_MACRO) {
+    // We will need to specify the path to find a procedural macro,
+    // but have no need to specify the paths to find its dependencies
+    // as the procedural macro is now a complete .so.
+    rust_values().transitive_libs().Append(dep, is_public);
+  } else if (dep->output_type() == SHARED_LIBRARY) {
+    // Shared library dependendencies are inherited across public shared
+    // library boundaries.
+    //
+    // In this case:
+    //   EXE -> INTERMEDIATE_SHLIB --[public]--> FINAL_SHLIB
+    // The EXE will also link to to FINAL_SHLIB. The public dependency means
+    // that the EXE can use the headers in FINAL_SHLIB so the FINAL_SHLIB
+    // will need to appear on EXE's link line.
+    //
+    // However, if the dependency is private:
+    //   EXE -> INTERMEDIATE_SHLIB --[private]--> FINAL_SHLIB
+    // the dependency will not be propagated because INTERMEDIATE_SHLIB is
+    // not granting permission to call functions from FINAL_SHLIB. If EXE
+    // wants to use functions (and link to) FINAL_SHLIB, it will need to do
+    // so explicitly.
+    //
+    // Static libraries and source sets aren't inherited across shared
+    // library boundaries because they will be linked into the shared
+    // library.
+    inherited_libraries_.AppendPublicSharedLibraries(dep->inherited_libraries(),
+                                                     is_public);
+  } else if (!dep->IsFinal()) {
+    // The current target isn't linked, so propagate linked deps and
+    // libraries up the dependency tree.
+    inherited_libraries_.AppendInherited(dep->inherited_libraries(), is_public);
+    rust_values().transitive_libs().AppendInherited(
+        dep->rust_values().transitive_libs(), is_public);
+  } else if (dep->complete_static_lib()) {
+    // Inherit only final targets through _complete_ static libraries.
+    //
+    // Inherited final libraries aren't linked into complete static libraries.
+    // They are forwarded here so that targets that depend on complete
+    // static libraries can link them in. Conversely, since complete static
+    // libraries link in non-final targets they shouldn't be inherited.
+    for (const auto& inherited :
+         dep->inherited_libraries().GetOrderedAndPublicFlag()) {
+      if (inherited.first->IsFinal()) {
+        inherited_libraries_.Append(inherited.first,
+                                    is_public && inherited.second);
+      }
+    }
+  }
+
+  // Library settings are always inherited across static library boundaries.
+  if (!dep->IsFinal() || dep->output_type() == STATIC_LIBRARY) {
+    all_lib_dirs_.append(dep->all_lib_dirs());
+    all_libs_.append(dep->all_libs());
+
+    all_framework_dirs_.append(dep->all_framework_dirs());
+    all_frameworks_.append(dep->all_frameworks());
+    all_weak_frameworks_.append(dep->all_weak_frameworks());
+  }
+}
+
+void Target::PullDependentTargetLibs() {
+  for (const auto& dep : public_deps_)
+    PullDependentTargetLibsFrom(dep.ptr, true);
+  for (const auto& dep : private_deps_)
+    PullDependentTargetLibsFrom(dep.ptr, false);
+}
+
+void Target::PullRecursiveHardDeps() {
+  for (const auto& pair : GetDeps(DEPS_LINKED)) {
+    // Direct hard dependencies.
+    if (hard_dep() || pair.ptr->hard_dep()) {
+      recursive_hard_deps_.insert(pair.ptr);
+      continue;
+    }
+
+    // If |pair.ptr| is binary target and |pair.ptr| has no public header,
+    // |this| target does not need to have |pair.ptr|'s hard_deps as its
+    // hard_deps to start compiles earlier. Unless the target compiles a
+    // Swift module (since they also generate a header that can be used
+    // by the current target).
+    if (pair.ptr->IsBinary() && !pair.ptr->all_headers_public() &&
+        pair.ptr->public_headers().empty() &&
+        !pair.ptr->swift_values().builds_module()) {
+      continue;
+    }
+
+    // Recursive hard dependencies of all dependencies.
+    recursive_hard_deps_.insert(pair.ptr->recursive_hard_deps().begin(),
+                                pair.ptr->recursive_hard_deps().end());
+  }
+}
+
+void Target::PullRecursiveBundleData() {
+  for (const auto& pair : GetDeps(DEPS_LINKED)) {
+    // Don't propagate bundle_data once they are added to a bundle.
+    if (pair.ptr->output_type() == CREATE_BUNDLE)
+      continue;
+
+    // Don't propagate across toolchain.
+    if (pair.ptr->toolchain() != toolchain())
+      continue;
+
+    // Direct dependency on a bundle_data target.
+    if (pair.ptr->output_type() == BUNDLE_DATA)
+      bundle_data_.AddBundleData(pair.ptr);
+
+    // Recursive bundle_data informations from all dependencies.
+    for (auto* target : pair.ptr->bundle_data().bundle_deps())
+      bundle_data_.AddBundleData(target);
+  }
+
+  bundle_data_.OnTargetResolved(this);
+}
+
+bool Target::FillOutputFiles(Err* err) {
+  const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+  bool check_tool_outputs = false;
+  switch (output_type_) {
+    case GROUP:
+    case BUNDLE_DATA:
+    case CREATE_BUNDLE:
+    case SOURCE_SET:
+    case COPY_FILES:
+    case ACTION:
+    case ACTION_FOREACH:
+    case GENERATED_FILE: {
+      // These don't get linked to and use stamps which should be the first
+      // entry in the outputs. These stamps are named
+      // "<target_out_dir>/<targetname>.stamp".
+      dependency_output_file_ =
+          GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
+      dependency_output_file_.value().append(GetComputedOutputName());
+      dependency_output_file_.value().append(".stamp");
+      break;
+    }
+    case EXECUTABLE:
+    case LOADABLE_MODULE:
+      // Executables and loadable modules don't get linked to, but the first
+      // output is used for dependency management.
+      CHECK_GE(tool->outputs().list().size(), 1u);
+      check_tool_outputs = true;
+      dependency_output_file_ =
+          SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+              this, tool, tool->outputs().list()[0]);
+
+      if (tool->runtime_outputs().list().empty()) {
+        // Default to the first output for the runtime output.
+        runtime_outputs_.push_back(dependency_output_file_);
+      } else {
+        SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+            this, tool, tool->runtime_outputs(), &runtime_outputs_);
+      }
+      break;
+    case RUST_LIBRARY:
+    case STATIC_LIBRARY:
+      // Static libraries both have dependencies and linking going off of the
+      // first output.
+      CHECK(tool->outputs().list().size() >= 1);
+      check_tool_outputs = true;
+      link_output_file_ = dependency_output_file_ =
+          SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+              this, tool, tool->outputs().list()[0]);
+      break;
+    case RUST_PROC_MACRO:
+    case SHARED_LIBRARY:
+      CHECK(tool->outputs().list().size() >= 1);
+      check_tool_outputs = true;
+      if (const CTool* ctool = tool->AsC()) {
+        if (ctool->link_output().empty() && ctool->depend_output().empty()) {
+          // Default behavior, use the first output file for both.
+          link_output_file_ = dependency_output_file_ =
+              SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                  this, tool, tool->outputs().list()[0]);
+        } else {
+          // Use the tool-specified ones.
+          if (!ctool->link_output().empty()) {
+            link_output_file_ =
+                SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                    this, tool, ctool->link_output());
+          }
+          if (!ctool->depend_output().empty()) {
+            dependency_output_file_ =
+                SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                    this, tool, ctool->depend_output());
+          }
+        }
+        if (tool->runtime_outputs().list().empty()) {
+          // Default to the link output for the runtime output.
+          runtime_outputs_.push_back(link_output_file_);
+        } else {
+          SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+              this, tool, tool->runtime_outputs(), &runtime_outputs_);
+        }
+      } else if (const RustTool* rstool = tool->AsRust()) {
+        // Default behavior, use the first output file for both.
+        link_output_file_ = dependency_output_file_ =
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                this, tool, tool->outputs().list()[0]);
+      }
+      break;
+    case UNKNOWN:
+    default:
+      NOTREACHED();
+  }
+
+  // Count anything generated from bundle_data dependencies.
+  if (output_type_ == CREATE_BUNDLE) {
+    if (!bundle_data_.GetOutputFiles(settings(), this, &computed_outputs_, err))
+      return false;
+  }
+
+  // Count all outputs from this tool as something generated by this target.
+  if (check_tool_outputs) {
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        this, tool, tool->outputs(), &computed_outputs_);
+
+    // Output names aren't canonicalized in the same way that source files
+    // are. For example, the tool outputs often use
+    // {{some_var}}/{{output_name}} which expands to "./foo", but this won't
+    // match "foo" which is what we'll compute when converting a SourceFile to
+    // an OutputFile.
+    for (auto& out : computed_outputs_)
+      NormalizePath(&out.value());
+  }
+
+  // Also count anything the target has declared to be an output.
+  std::vector<SourceFile> outputs_as_sources;
+  action_values_.GetOutputsAsSourceFiles(this, &outputs_as_sources);
+  for (const SourceFile& out : outputs_as_sources)
+    computed_outputs_.push_back(OutputFile(settings()->build_settings(), out));
+
+  return true;
+}
+
+bool Target::ResolvePrecompiledHeaders(Err* err) {
+  // Precompiled headers are stored on a ConfigValues struct. This way, the
+  // build can set all the precompiled header settings in a config and apply
+  // it to many targets. Likewise, the precompiled header values may be
+  // specified directly on a target.
+  //
+  // Unlike other values on configs which are lists that just get concatenated,
+  // the precompiled header settings are unique values. We allow them to be
+  // specified anywhere, but if they are specified in more than one place all
+  // places must match.
+
+  // Track where the current settings came from for issuing errors.
+  const Label* pch_header_settings_from = NULL;
+  if (config_values_.has_precompiled_headers())
+    pch_header_settings_from = &label();
+
+  for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
+    if (!iter.GetCurrentConfig())
+      continue;  // Skip the one on the target itself.
+
+    const Config* config = iter.GetCurrentConfig();
+    const ConfigValues& cur = config->resolved_values();
+    if (!cur.has_precompiled_headers())
+      continue;  // This one has no precompiled header info, skip.
+
+    if (config_values_.has_precompiled_headers()) {
+      // Already have a precompiled header values, the settings must match.
+      if (config_values_.precompiled_header() != cur.precompiled_header() ||
+          config_values_.precompiled_source() != cur.precompiled_source()) {
+        *err = Err(
+            defined_from(), "Precompiled header setting conflict.",
+            "The target " + label().GetUserVisibleName(false) +
+                "\n"
+                "has conflicting precompiled header settings.\n"
+                "\n"
+                "From " +
+                pch_header_settings_from->GetUserVisibleName(false) +
+                "\n  header: " + config_values_.precompiled_header() +
+                "\n  source: " + config_values_.precompiled_source().value() +
+                "\n\n"
+                "From " +
+                config->label().GetUserVisibleName(false) +
+                "\n  header: " + cur.precompiled_header() +
+                "\n  source: " + cur.precompiled_source().value());
+        return false;
+      }
+    } else {
+      // Have settings from a config, apply them to ourselves.
+      pch_header_settings_from = &config->label();
+      config_values_.set_precompiled_header(cur.precompiled_header());
+      config_values_.set_precompiled_source(cur.precompiled_source());
+    }
+  }
+
+  return true;
+}
+
+bool Target::CheckVisibility(Err* err) const {
+  for (const auto& pair : GetDeps(DEPS_ALL)) {
+    if (!Visibility::CheckItemVisibility(this, pair.ptr, err))
+      return false;
+  }
+  return true;
+}
+
+bool Target::CheckConfigVisibility(Err* err) const {
+  for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
+    if (const Config* config = iter.GetCurrentConfig())
+      if (!Visibility::CheckItemVisibility(this, config, err))
+        return false;
+  }
+  return true;
+}
+
+bool Target::CheckSourceSetLanguages(Err* err) const {
+  if (output_type() == Target::SOURCE_SET &&
+      source_types_used().RustSourceUsed()) {
+    *err = Err(defined_from(), "source_set contained Rust code.",
+               label().GetUserVisibleName(false) +
+                   " has Rust code. Only C/C++ source_sets are supported.");
+    return false;
+  }
+  return true;
+}
+
+bool Target::CheckTestonly(Err* err) const {
+  // If the current target is marked testonly, it can include both testonly
+  // and non-testonly targets, so there's nothing to check.
+  if (testonly())
+    return true;
+
+  // Verify no deps have "testonly" set.
+  for (const auto& pair : GetDeps(DEPS_ALL)) {
+    if (pair.ptr->testonly()) {
+      *err = MakeTestOnlyError(this, pair.ptr);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Target::CheckAssertNoDeps(Err* err) const {
+  if (assert_no_deps_.empty())
+    return true;
+
+  std::set<const Target*> visited;
+  std::string failure_path_str;
+  const LabelPattern* failure_pattern = nullptr;
+
+  if (!RecursiveCheckAssertNoDeps(this, false, assert_no_deps_, &visited,
+                                  &failure_path_str, &failure_pattern)) {
+    *err = Err(
+        defined_from(), "assert_no_deps failed.",
+        label().GetUserVisibleName(false) +
+            " has an assert_no_deps entry:\n  " + failure_pattern->Describe() +
+            "\nwhich fails for the dependency path:\n" + failure_path_str);
+    return false;
+  }
+  return true;
+}
+
+void Target::CheckSourcesGenerated() const {
+  // Checks that any inputs or sources to this target that are in the build
+  // directory are generated by a target that this one transitively depends on
+  // in some way. We already guarantee that all generated files are written
+  // to the build dir.
+  //
+  // See Scheduler::AddUnknownGeneratedInput's declaration for more.
+  for (const SourceFile& file : sources_)
+    CheckSourceGenerated(file);
+  for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
+    for (const SourceFile& file : iter.cur().inputs())
+      CheckSourceGenerated(file);
+  }
+  // TODO(agrieve): Check all_libs_ here as well (those that are source files).
+  // http://crbug.com/571731
+}
+
+void Target::CheckSourceGenerated(const SourceFile& source) const {
+  if (!IsStringInOutputDir(settings()->build_settings()->build_dir(),
+                           source.value()))
+    return;  // Not in output dir, this is OK.
+
+  // Tell the scheduler about unknown files. This will be noted for later so
+  // the list of files written by the GN build itself (often response files)
+  // can be filtered out of this list.
+  OutputFile out_file(settings()->build_settings(), source);
+  std::set<const Target*> seen_targets;
+  bool check_data_deps = false;
+  bool consider_object_files = false;
+  if (!EnsureFileIsGeneratedByDependency(this, out_file, true,
+                                         consider_object_files, check_data_deps,
+                                         &seen_targets)) {
+    seen_targets.clear();
+    // Allow dependency to be through data_deps for files generated by gn.
+    check_data_deps =
+        g_scheduler->IsFileGeneratedByWriteRuntimeDeps(out_file) ||
+        g_scheduler->IsFileGeneratedByTarget(source);
+    // Check object files (much slower and very rare) only if the "normal"
+    // output check failed.
+    consider_object_files = !check_data_deps;
+    if (!EnsureFileIsGeneratedByDependency(this, out_file, true,
+                                           consider_object_files,
+                                           check_data_deps, &seen_targets))
+      g_scheduler->AddUnknownGeneratedInput(this, source);
+  }
+}
+
+bool Target::GetMetadata(const std::vector<std::string>& keys_to_extract,
+                         const std::vector<std::string>& keys_to_walk,
+                         const SourceDir& rebase_dir,
+                         bool deps_only,
+                         std::vector<Value>* result,
+                         std::set<const Target*>* targets_walked,
+                         Err* err) const {
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> current_result;
+  // If deps_only, this is the top-level target and thus we don't want to
+  // collect its metadata, only that of its deps and data_deps.
+  if (deps_only) {
+    // Empty string will be converted below to mean all deps and data_deps.
+    // Origin is null because this isn't declared anywhere, and should never
+    // trigger any errors.
+    next_walk_keys.push_back(Value(nullptr, ""));
+  } else {
+    // Otherwise, we walk this target and collect the appropriate data.
+    if (!metadata_.WalkStep(settings()->build_settings(), keys_to_extract,
+                            keys_to_walk, rebase_dir, &next_walk_keys,
+                            &current_result, err))
+      return false;
+  }
+
+  // Gather walk keys and find the appropriate target. Targets identified in
+  // the walk key set must be deps or data_deps of the declaring target.
+  const DepsIteratorRange& all_deps = GetDeps(Target::DEPS_ALL);
+  const SourceDir& current_dir = label().dir();
+  for (const auto& next : next_walk_keys) {
+    DCHECK(next.type() == Value::STRING);
+
+    // If we hit an empty string in this list, add all deps and data_deps. The
+    // ordering in the resulting list of values as a result will be the data
+    // from each explicitly listed dep prior to this, followed by all data in
+    // walk order of the remaining deps.
+    if (next.string_value().empty()) {
+      for (const auto& dep : all_deps) {
+        // If we haven't walked this dep yet, go down into it.
+        auto pair = targets_walked->insert(dep.ptr);
+        if (pair.second) {
+          if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir,
+                                    false, result, targets_walked, err))
+            return false;
+        }
+      }
+
+      // Any other walk keys are superfluous, as they can only be a subset of
+      // all deps.
+      break;
+    }
+
+    // Otherwise, look through the target's deps for the specified one.
+    // Canonicalize the label if possible.
+    Label next_label = Label::Resolve(
+        current_dir, settings()->build_settings()->root_path_utf8(),
+        settings()->toolchain_label(), next, err);
+    if (next_label.is_null()) {
+      *err = Err(next.origin(), std::string("Failed to canonicalize ") +
+                                    next.string_value() + std::string("."));
+    }
+    std::string canonicalize_next_label = next_label.GetUserVisibleName(true);
+
+    bool found_next = false;
+    for (const auto& dep : all_deps) {
+      // Match against the label with the toolchain.
+      if (dep.label.GetUserVisibleName(true) == canonicalize_next_label) {
+        // If we haven't walked this dep yet, go down into it.
+        auto pair = targets_walked->insert(dep.ptr);
+        if (pair.second) {
+          if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_dir,
+                                    false, result, targets_walked, err))
+            return false;
+        }
+        // We found it, so we can exit this search now.
+        found_next = true;
+        break;
+      }
+    }
+    // If we didn't find the specified dep in the target, that's an error.
+    // Propagate it back to the user.
+    if (!found_next) {
+      *err = Err(next.origin(),
+                 std::string("I was expecting ") + canonicalize_next_label +
+                     std::string(" to be a dependency of ") +
+                     label().GetUserVisibleName(true) +
+                     ". Make sure it's included in the deps or data_deps, and "
+                     "that you've specified the appropriate toolchain.");
+      return false;
+    }
+  }
+  result->insert(result->end(), std::make_move_iterator(current_result.begin()),
+                 std::make_move_iterator(current_result.end()));
+  return true;
+}
diff --git a/src/gn/target.h b/src/gn/target.h
new file mode 100644 (file)
index 0000000..15fb635
--- /dev/null
@@ -0,0 +1,518 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TARGET_H_
+#define TOOLS_GN_TARGET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/action_values.h"
+#include "gn/bundle_data.h"
+#include "gn/config_values.h"
+#include "gn/inherited_libraries.h"
+#include "gn/item.h"
+#include "gn/label_pattern.h"
+#include "gn/label_ptr.h"
+#include "gn/lib_file.h"
+#include "gn/metadata.h"
+#include "gn/ordered_set.h"
+#include "gn/output_file.h"
+#include "gn/rust_values.h"
+#include "gn/source_file.h"
+#include "gn/swift_values.h"
+#include "gn/toolchain.h"
+#include "gn/unique_vector.h"
+
+class DepsIteratorRange;
+class Settings;
+class Toolchain;
+
+class Target : public Item {
+ public:
+  enum OutputType {
+    UNKNOWN,
+    GROUP,
+    EXECUTABLE,
+    SHARED_LIBRARY,
+    LOADABLE_MODULE,
+    STATIC_LIBRARY,
+    SOURCE_SET,
+    COPY_FILES,
+    ACTION,
+    ACTION_FOREACH,
+    BUNDLE_DATA,
+    CREATE_BUNDLE,
+    GENERATED_FILE,
+    RUST_LIBRARY,
+    RUST_PROC_MACRO,
+  };
+
+  enum DepsIterationType {
+    DEPS_ALL,     // Iterates through all public, private, and data deps.
+    DEPS_LINKED,  // Iterates through all non-data dependencies.
+  };
+
+  using FileList = std::vector<SourceFile>;
+  using StringVector = std::vector<std::string>;
+
+  // We track the set of build files that may affect this target, please refer
+  // to Scope for how this is determined.
+  Target(const Settings* settings,
+         const Label& label,
+         const SourceFileSet& build_dependency_files = {});
+  ~Target() override;
+
+  // Returns a string naming the output type.
+  static const char* GetStringForOutputType(OutputType type);
+
+  // Item overrides.
+  Target* AsTarget() override;
+  const Target* AsTarget() const override;
+  bool OnResolved(Err* err) override;
+
+  OutputType output_type() const { return output_type_; }
+  void set_output_type(OutputType t) { output_type_ = t; }
+
+  // True for targets that compile source code (all types of libraries and
+  // executables).
+  bool IsBinary() const;
+
+  // Can be linked into other targets.
+  bool IsLinkable() const;
+
+  // True if the target links dependencies rather than propagated up the graph.
+  // This is also true of action and copy steps even though they don't link
+  // dependencies, because they also don't propagate libraries up.
+  bool IsFinal() const;
+
+  // Set when the target should normally be treated as a data dependency. These
+  // do not need to be treated as inputs or hard dependencies for normal build
+  // steps, but have to be kept in the dependency tree to be properly
+  // propagated. Treating these as data only decreases superfluous rebuilds and
+  // increases parallelism.
+  bool IsDataOnly() const;
+
+  // Will be the empty string to use the target label as the output name.
+  // See GetComputedOutputName().
+  const std::string& output_name() const { return output_name_; }
+  void set_output_name(const std::string& name) { output_name_ = name; }
+
+  // Returns the output name for this target, which is the output_name if
+  // specified, or the target label if not.
+  //
+  // Because this depends on the tool for this target, the toolchain must
+  // have been set before calling.
+  std::string GetComputedOutputName() const;
+
+  bool output_prefix_override() const { return output_prefix_override_; }
+  void set_output_prefix_override(bool prefix_override) {
+    output_prefix_override_ = prefix_override;
+  }
+
+  // Desired output directory for the final output. This will be used for
+  // the {{output_dir}} substitution in the tool if it is specified. If
+  // is_null, the tool default will be used.
+  const SourceDir& output_dir() const { return output_dir_; }
+  void set_output_dir(const SourceDir& dir) { output_dir_ = dir; }
+
+  // The output extension is really a tri-state: unset (output_extension_set
+  // is false and the string is empty, meaning the default extension should be
+  // used), the output extension is set but empty (output should have no
+  // extension) and the output extension is set but nonempty (use the given
+  // extension).
+  const std::string& output_extension() const { return output_extension_; }
+  void set_output_extension(const std::string& extension) {
+    output_extension_ = extension;
+    output_extension_set_ = true;
+  }
+  bool output_extension_set() const { return output_extension_set_; }
+
+  const FileList& sources() const { return sources_; }
+  FileList& sources() { return sources_; }
+
+  const SourceFileTypeSet& source_types_used() const {
+    return source_types_used_;
+  }
+  SourceFileTypeSet& source_types_used() { return source_types_used_; }
+
+  // Set to true when all sources are public. This is the default. In this case
+  // the public headers list should be empty.
+  bool all_headers_public() const { return all_headers_public_; }
+  void set_all_headers_public(bool p) { all_headers_public_ = p; }
+
+  // When all_headers_public is false, this is the list of public headers. It
+  // could be empty which would mean no headers are public.
+  const FileList& public_headers() const { return public_headers_; }
+  FileList& public_headers() { return public_headers_; }
+
+  // Whether this target's includes should be checked by "gn check".
+  bool check_includes() const { return check_includes_; }
+  void set_check_includes(bool ci) { check_includes_ = ci; }
+
+  // Whether this static_library target should have code linked in.
+  bool complete_static_lib() const { return complete_static_lib_; }
+  void set_complete_static_lib(bool complete) {
+    DCHECK_EQ(STATIC_LIBRARY, output_type_);
+    complete_static_lib_ = complete;
+  }
+
+  // Metadata. Target takes ownership of the resulting scope.
+  const Metadata& metadata() const { return metadata_; }
+  Metadata& metadata() { return metadata_; }
+
+  // Get metadata from this target and its dependencies. This is intended to
+  // be called after the target is resolved.
+  bool GetMetadata(const std::vector<std::string>& keys_to_extract,
+                   const std::vector<std::string>& keys_to_walk,
+                   const SourceDir& rebase_dir,
+                   bool deps_only,
+                   std::vector<Value>* result,
+                   std::set<const Target*>* targets_walked,
+                   Err* err) const;
+
+  // GeneratedFile-related methods.
+  bool GenerateFile(Err* err) const;
+
+  const Value& contents() const { return contents_; }
+  void set_contents(const Value& value) { contents_ = value; }
+  const Value& output_conversion() const { return output_conversion_; }
+  void set_output_conversion(const Value& value) { output_conversion_ = value; }
+
+  // Metadata collection methods for GeneratedFile targets.
+  const SourceDir& rebase() const { return rebase_; }
+  void set_rebase(const SourceDir& value) { rebase_ = value; }
+  const std::vector<std::string>& data_keys() const { return data_keys_; }
+  std::vector<std::string>& data_keys() { return data_keys_; }
+  const std::vector<std::string>& walk_keys() const { return walk_keys_; }
+  std::vector<std::string>& walk_keys() { return walk_keys_; }
+
+  bool testonly() const { return testonly_; }
+  void set_testonly(bool value) { testonly_ = value; }
+
+  OutputFile write_runtime_deps_output() const {
+    return write_runtime_deps_output_;
+  }
+  void set_write_runtime_deps_output(const OutputFile& value) {
+    write_runtime_deps_output_ = value;
+  }
+
+  // Runtime dependencies. These are "file-like things" that can either be
+  // directories or files. They do not need to exist, these are just passed as
+  // runtime dependencies to external test systems as necessary.
+  const std::vector<std::string>& data() const { return data_; }
+  std::vector<std::string>& data() { return data_; }
+
+  // Information about the bundle. Only valid for CREATE_BUNDLE target after
+  // they have been resolved.
+  const BundleData& bundle_data() const { return bundle_data_; }
+  BundleData& bundle_data() { return bundle_data_; }
+
+  // Returns true if targets depending on this one should have an order
+  // dependency.
+  bool hard_dep() const {
+    return output_type_ == ACTION || output_type_ == ACTION_FOREACH ||
+           output_type_ == COPY_FILES || output_type_ == CREATE_BUNDLE ||
+           output_type_ == BUNDLE_DATA || output_type_ == GENERATED_FILE ||
+           (IsBinary() && swift_values().builds_module());
+  }
+
+  // Returns the iterator range which can be used in range-based for loops
+  // to iterate over multiple types of deps in one loop:
+  //   for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) ...
+  DepsIteratorRange GetDeps(DepsIterationType type) const;
+
+  // Linked private dependencies.
+  const LabelTargetVector& private_deps() const { return private_deps_; }
+  LabelTargetVector& private_deps() { return private_deps_; }
+
+  // Linked public dependencies.
+  const LabelTargetVector& public_deps() const { return public_deps_; }
+  LabelTargetVector& public_deps() { return public_deps_; }
+
+  // Non-linked dependencies.
+  const LabelTargetVector& data_deps() const { return data_deps_; }
+  LabelTargetVector& data_deps() { return data_deps_; }
+
+  // List of configs that this class inherits settings from. Once a target is
+  // resolved, this will also list all-dependent and public configs.
+  const UniqueVector<LabelConfigPair>& configs() const { return configs_; }
+  UniqueVector<LabelConfigPair>& configs() { return configs_; }
+
+  // List of configs that all dependencies (direct and indirect) of this
+  // target get. These configs are not added to this target. Note that due
+  // to the way this is computed, there may be duplicates in this list.
+  const UniqueVector<LabelConfigPair>& all_dependent_configs() const {
+    return all_dependent_configs_;
+  }
+  UniqueVector<LabelConfigPair>& all_dependent_configs() {
+    return all_dependent_configs_;
+  }
+
+  // List of configs that targets depending directly on this one get. These
+  // configs are also added to this target.
+  const UniqueVector<LabelConfigPair>& public_configs() const {
+    return public_configs_;
+  }
+  UniqueVector<LabelConfigPair>& public_configs() { return public_configs_; }
+
+  // Dependencies that can include files from this target.
+  const std::set<Label>& allow_circular_includes_from() const {
+    return allow_circular_includes_from_;
+  }
+  std::set<Label>& allow_circular_includes_from() {
+    return allow_circular_includes_from_;
+  }
+
+  const InheritedLibraries& inherited_libraries() const {
+    return inherited_libraries_;
+  }
+
+  // This config represents the configuration set directly on this target.
+  ConfigValues& config_values() { return config_values_; }
+  const ConfigValues& config_values() const { return config_values_; }
+
+  ActionValues& action_values() { return action_values_; }
+  const ActionValues& action_values() const { return action_values_; }
+
+  SwiftValues& swift_values() { return swift_values_; }
+  const SwiftValues& swift_values() const { return swift_values_; }
+
+  RustValues& rust_values() { return rust_values_; }
+  const RustValues& rust_values() const { return rust_values_; }
+
+  const OrderedSet<SourceDir>& all_lib_dirs() const { return all_lib_dirs_; }
+  const OrderedSet<LibFile>& all_libs() const { return all_libs_; }
+
+  const OrderedSet<SourceDir>& all_framework_dirs() const {
+    return all_framework_dirs_;
+  }
+  const OrderedSet<std::string>& all_frameworks() const {
+    return all_frameworks_;
+  }
+  const OrderedSet<std::string>& all_weak_frameworks() const {
+    return all_weak_frameworks_;
+  }
+
+  const std::set<const Target*>& recursive_hard_deps() const {
+    return recursive_hard_deps_;
+  }
+
+  std::vector<LabelPattern>& friends() { return friends_; }
+  const std::vector<LabelPattern>& friends() const { return friends_; }
+
+  std::vector<LabelPattern>& assert_no_deps() { return assert_no_deps_; }
+  const std::vector<LabelPattern>& assert_no_deps() const {
+    return assert_no_deps_;
+  }
+
+  // The toolchain is only known once this target is resolved (all if its
+  // dependencies are known). They will be null until then. Generally, this can
+  // only be used during target writing.
+  const Toolchain* toolchain() const { return toolchain_; }
+
+  // Sets the toolchain. The toolchain must include a tool for this target
+  // or the error will be set and the function will return false. Unusually,
+  // this function's "err" output is optional since this is commonly used
+  // frequently by unit tests which become needlessly verbose.
+  bool SetToolchain(const Toolchain* toolchain, Err* err = nullptr);
+
+  // Once this target has been resolved, all outputs from the target will be
+  // listed here. This will include things listed in the "outputs" for an
+  // action or a copy step, and the output library or executable file(s) from
+  // binary targets.
+  //
+  // It will NOT include stamp files and object files.
+  const std::vector<OutputFile>& computed_outputs() const {
+    return computed_outputs_;
+  }
+
+  // Returns outputs from this target. The link output file is the one that
+  // other targets link to when they depend on this target. This will only be
+  // valid for libraries and will be empty for all other target types.
+  //
+  // The dependency output file is the file that should be used to express
+  // a dependency on this one. It could be the same as the link output file
+  // (this will be the case for static libraries). For shared libraries it
+  // could be the same or different than the link output file, depending on the
+  // system. For actions this will be the stamp file.
+  //
+  // These are only known once the target is resolved and will be empty before
+  // that. This is a cache of the files to prevent every target that depends on
+  // a given library from recomputing the same pattern.
+  const OutputFile& link_output_file() const { return link_output_file_; }
+  const OutputFile& dependency_output_file() const {
+    return dependency_output_file_;
+  }
+
+  // The subset of computed_outputs that are considered runtime outputs.
+  const std::vector<OutputFile>& runtime_outputs() const {
+    return runtime_outputs_;
+  }
+
+  // Computes and returns the outputs of this target expressed as SourceFiles.
+  //
+  // For binary target this depends on the tool for this target so the toolchain
+  // must have been loaded beforehand. This will happen asynchronously so
+  // calling this on a binary target before the build is complete will produce a
+  // race condition.
+  //
+  // To resolve this, the caller passes in whether the entire build is complete
+  // (this is used for the introspection commands which run after everything
+  // else).
+  //
+  // If the build is complete, the toolchain will be used for binary targets to
+  // compute the outputs. If the build is not complete, calling this function
+  // for binary targets will produce an error.
+  //
+  // The |loc_for_error| is used to blame a location for any errors produced. It
+  // can be empty if there is no range (like this is being called based on the
+  // command-line.
+  bool GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
+                               bool build_complete,
+                               std::vector<SourceFile>* outputs,
+                               Err* err) const;
+
+  // Computes the set of output files resulting from compiling the given source
+  // file.
+  //
+  // For binary targets, if the file can be compiled and the tool exists, fills
+  // the outputs in and writes the tool type to computed_tool_type. If the file
+  // is not compilable, returns false.
+  //
+  // For action_foreach and copy targets, applies the output pattern to the
+  // given file name to compute the outputs.
+  //
+  // For all other target types, just returns the target outputs because such
+  // targets conceptually process all of their inputs as one step.
+  //
+  // The function can succeed with a "NONE" tool type for object files which
+  // are just passed to the output. The output will always be overwritten, not
+  // appended to.
+  bool GetOutputFilesForSource(const SourceFile& source,
+                               const char** computed_tool_type,
+                               std::vector<OutputFile>* outputs) const;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(TargetTest, ResolvePrecompiledHeaders);
+
+  // Pulls necessary information from dependencies to this one when all
+  // dependencies have been resolved.
+  void PullDependentTargetConfigs();
+  void PullDependentTargetLibsFrom(const Target* dep, bool is_public);
+  void PullDependentTargetLibs();
+  void PullRecursiveHardDeps();
+  void PullRecursiveBundleData();
+
+  // Fills the link and dependency output files when a target is resolved.
+  bool FillOutputFiles(Err* err);
+
+  // Checks precompiled headers from configs and makes sure the resulting
+  // values are in config_values_.
+  bool ResolvePrecompiledHeaders(Err* err);
+
+  // Validates the given thing when a target is resolved.
+  bool CheckVisibility(Err* err) const;
+  bool CheckConfigVisibility(Err* err) const;
+  bool CheckTestonly(Err* err) const;
+  bool CheckAssertNoDeps(Err* err) const;
+  void CheckSourcesGenerated() const;
+  void CheckSourceGenerated(const SourceFile& source) const;
+  bool CheckSourceSetLanguages(Err* err) const;
+
+  OutputType output_type_ = UNKNOWN;
+  std::string output_name_;
+  bool output_prefix_override_ = false;
+  SourceDir output_dir_;
+  std::string output_extension_;
+  bool output_extension_set_ = false;
+
+  FileList sources_;
+  SourceFileTypeSet source_types_used_;
+  bool all_headers_public_ = true;
+  FileList public_headers_;
+  bool check_includes_ = true;
+  bool complete_static_lib_ = false;
+  bool testonly_ = false;
+  std::vector<std::string> data_;
+  BundleData bundle_data_;
+  OutputFile write_runtime_deps_output_;
+
+  LabelTargetVector private_deps_;
+  LabelTargetVector public_deps_;
+  LabelTargetVector data_deps_;
+
+  // See getters for more info.
+  UniqueVector<LabelConfigPair> configs_;
+  UniqueVector<LabelConfigPair> all_dependent_configs_;
+  UniqueVector<LabelConfigPair> public_configs_;
+
+  std::set<Label> allow_circular_includes_from_;
+
+  // Static libraries, shared libraries, and source sets from transitive deps
+  // that need to be linked.
+  InheritedLibraries inherited_libraries_;
+
+  // These libs and dirs are inherited from statically linked deps and all
+  // configs applying to this target.
+  OrderedSet<SourceDir> all_lib_dirs_;
+  OrderedSet<LibFile> all_libs_;
+
+  // These frameworks and dirs are inherited from statically linked deps and
+  // all configs applying to this target.
+  OrderedSet<SourceDir> all_framework_dirs_;
+  OrderedSet<std::string> all_frameworks_;
+  OrderedSet<std::string> all_weak_frameworks_;
+
+  // All hard deps from this target and all dependencies. Filled in when this
+  // target is marked resolved. This will not include the current target.
+  std::set<const Target*> recursive_hard_deps_;
+
+  std::vector<LabelPattern> friends_;
+  std::vector<LabelPattern> assert_no_deps_;
+
+  // Used for all binary targets, and for inputs in regular targets. The
+  // precompiled header values in this struct will be resolved to the ones to
+  // use for this target, if precompiled headers are used.
+  ConfigValues config_values_;
+
+  // Used for action[_foreach] targets.
+  ActionValues action_values_;
+
+  // Used for Rust targets.
+  RustValues rust_values_;
+
+  // User for Swift targets.
+  SwiftValues swift_values_;
+
+  // Toolchain used by this target. Null until target is resolved.
+  const Toolchain* toolchain_ = nullptr;
+
+  // Output files. Empty until the target is resolved.
+  std::vector<OutputFile> computed_outputs_;
+  OutputFile link_output_file_;
+  OutputFile dependency_output_file_;
+  std::vector<OutputFile> runtime_outputs_;
+
+  Metadata metadata_;
+
+  // GeneratedFile values.
+  Value output_conversion_;
+  Value contents_;  // Value::NONE if metadata collection should occur.
+
+  // GeneratedFile as metadata collection values.
+  SourceDir rebase_;
+  std::vector<std::string> data_keys_;
+  std::vector<std::string> walk_keys_;
+
+  DISALLOW_COPY_AND_ASSIGN(Target);
+};
+
+extern const char kExecution_Help[];
+
+#endif  // TOOLS_GN_TARGET_H_
diff --git a/src/gn/target_generator.cc b/src/gn/target_generator.cc
new file mode 100644 (file)
index 0000000..0ae9daf
--- /dev/null
@@ -0,0 +1,451 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/target_generator.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "gn/action_target_generator.h"
+#include "gn/binary_target_generator.h"
+#include "gn/build_settings.h"
+#include "gn/bundle_data_target_generator.h"
+#include "gn/config.h"
+#include "gn/copy_target_generator.h"
+#include "gn/create_bundle_target_generator.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/generated_file_target_generator.h"
+#include "gn/group_target_generator.h"
+#include "gn/metadata.h"
+#include "gn/parse_tree.h"
+#include "gn/scheduler.h"
+#include "gn/scope.h"
+#include "gn/token.h"
+#include "gn/value.h"
+#include "gn/value_extractors.h"
+#include "gn/variables.h"
+
+TargetGenerator::TargetGenerator(Target* target,
+                                 Scope* scope,
+                                 const FunctionCallNode* function_call,
+                                 Err* err)
+    : target_(target),
+      scope_(scope),
+      function_call_(function_call),
+      err_(err) {}
+
+TargetGenerator::~TargetGenerator() = default;
+
+void TargetGenerator::Run() {
+  // All target types use these.
+  if (!FillDependentConfigs())
+    return;
+
+  if (!FillData())
+    return;
+
+  if (!FillDependencies())
+    return;
+
+  if (!FillMetadata())
+    return;
+
+  if (!FillTestonly())
+    return;
+
+  if (!FillAssertNoDeps())
+    return;
+
+  if (!Visibility::FillItemVisibility(target_, scope_, err_))
+    return;
+
+  if (!FillWriteRuntimeDeps())
+    return;
+
+  // Do type-specific generation.
+  DoRun();
+}
+
+// static
+void TargetGenerator::GenerateTarget(Scope* scope,
+                                     const FunctionCallNode* function_call,
+                                     const std::vector<Value>& args,
+                                     const std::string& output_type,
+                                     Err* err) {
+  // Name is the argument to the function.
+  if (args.size() != 1u || args[0].type() != Value::STRING) {
+    *err = Err(function_call, "Target generator requires one string argument.",
+               "Otherwise I'm not sure what to call this target.");
+    return;
+  }
+
+  // The location of the target is the directory name with no slash at the end.
+  // FIXME(brettw) validate name.
+  const Label& toolchain_label = ToolchainLabelForScope(scope);
+  Label label(scope->GetSourceDir(), args[0].string_value(),
+              toolchain_label.dir(), toolchain_label.name());
+
+  if (g_scheduler->verbose_logging())
+    g_scheduler->Log("Defining target", label.GetUserVisibleName(true));
+
+  std::unique_ptr<Target> target = std::make_unique<Target>(
+      scope->settings(), label, scope->build_dependency_files());
+  target->set_defined_from(function_call);
+
+  // Create and call out to the proper generator.
+  if (output_type == functions::kBundleData) {
+    BundleDataTargetGenerator generator(target.get(), scope, function_call,
+                                        err);
+    generator.Run();
+  } else if (output_type == functions::kCreateBundle) {
+    CreateBundleTargetGenerator generator(target.get(), scope, function_call,
+                                          err);
+    generator.Run();
+  } else if (output_type == functions::kCopy) {
+    CopyTargetGenerator generator(target.get(), scope, function_call, err);
+    generator.Run();
+  } else if (output_type == functions::kAction) {
+    ActionTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::ACTION, err);
+    generator.Run();
+  } else if (output_type == functions::kActionForEach) {
+    ActionTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::ACTION_FOREACH, err);
+    generator.Run();
+  } else if (output_type == functions::kExecutable) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::EXECUTABLE, err);
+    generator.Run();
+  } else if (output_type == functions::kGroup) {
+    GroupTargetGenerator generator(target.get(), scope, function_call, err);
+    generator.Run();
+  } else if (output_type == functions::kLoadableModule) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::LOADABLE_MODULE, err);
+    generator.Run();
+  } else if (output_type == functions::kSharedLibrary) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::SHARED_LIBRARY, err);
+    generator.Run();
+  } else if (output_type == functions::kSourceSet) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::SOURCE_SET, err);
+    generator.Run();
+  } else if (output_type == functions::kStaticLibrary) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::STATIC_LIBRARY, err);
+    generator.Run();
+  } else if (output_type == functions::kGeneratedFile) {
+    GeneratedFileTargetGenerator generator(target.get(), scope, function_call,
+                                           Target::GENERATED_FILE, err);
+    generator.Run();
+  } else if (output_type == functions::kRustLibrary) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::RUST_LIBRARY, err);
+    generator.Run();
+  } else if (output_type == functions::kRustProcMacro) {
+    BinaryTargetGenerator generator(target.get(), scope, function_call,
+                                    Target::RUST_PROC_MACRO, err);
+    generator.Run();
+  } else {
+    *err = Err(function_call, "Not a known target type",
+               "I am very confused by the target type \"" + output_type + "\"");
+  }
+
+  if (err->has_error())
+    return;
+
+  // Save this target for the file.
+  Scope::ItemVector* collector = scope->GetItemCollector();
+  if (!collector) {
+    *err = Err(function_call, "Can't define a target in this context.");
+    return;
+  }
+  collector->push_back(std::move(target));
+}
+
+const BuildSettings* TargetGenerator::GetBuildSettings() const {
+  return scope_->settings()->build_settings();
+}
+
+bool TargetGenerator::FillSources() {
+  const Value* value = scope_->GetValue(variables::kSources, true);
+  if (!value)
+    return true;
+
+  Target::FileList dest_sources;
+  if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+                                  scope_->GetSourceDir(), &dest_sources, err_))
+    return false;
+  target_->sources() = std::move(dest_sources);
+  return true;
+}
+
+bool TargetGenerator::FillPublic() {
+  const Value* value = scope_->GetValue(variables::kPublic, true);
+  if (!value)
+    return true;
+
+  // If the public headers are defined, don't default to public.
+  target_->set_all_headers_public(false);
+
+  Target::FileList dest_public;
+  if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+                                  scope_->GetSourceDir(), &dest_public, err_))
+    return false;
+  target_->public_headers() = std::move(dest_public);
+  return true;
+}
+
+bool TargetGenerator::FillConfigs() {
+  return FillGenericConfigs(variables::kConfigs, &target_->configs());
+}
+
+bool TargetGenerator::FillDependentConfigs() {
+  if (!FillGenericConfigs(variables::kAllDependentConfigs,
+                          &target_->all_dependent_configs()))
+    return false;
+
+  if (!FillGenericConfigs(variables::kPublicConfigs,
+                          &target_->public_configs()))
+    return false;
+
+  return true;
+}
+
+bool TargetGenerator::FillData() {
+  const Value* value = scope_->GetValue(variables::kData, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  const std::vector<Value>& input_list = value->list_value();
+  std::vector<std::string>& output_list = target_->data();
+  output_list.reserve(input_list.size());
+
+  const SourceDir& dir = scope_->GetSourceDir();
+  const std::string& root_path =
+      scope_->settings()->build_settings()->root_path_utf8();
+
+  for (size_t i = 0; i < input_list.size(); i++) {
+    const Value& input = input_list[i];
+    if (!input.VerifyTypeIs(Value::STRING, err_))
+      return false;
+    const std::string input_str = input.string_value();
+
+    // Treat each input as either a file or a directory, depending on the
+    // last character.
+    bool as_dir = !input_str.empty() && input_str[input_str.size() - 1] == '/';
+
+    std::string resolved =
+        dir.ResolveRelativeAs(!as_dir, input, err_, root_path, &input_str);
+    if (err_->has_error())
+      return false;
+
+    output_list.push_back(resolved);
+  }
+  return true;
+}
+
+bool TargetGenerator::FillDependencies() {
+  if (!FillGenericDeps(variables::kDeps, &target_->private_deps()))
+    return false;
+  if (!FillGenericDeps(variables::kPublicDeps, &target_->public_deps()))
+    return false;
+  if (!FillGenericDeps(variables::kDataDeps, &target_->data_deps()))
+    return false;
+
+  // "data_deps" was previously named "datadeps". For backwards-compat, read
+  // the old one if no "data_deps" were specified.
+  if (!scope_->GetValue(variables::kDataDeps, false)) {
+    if (!FillGenericDeps("datadeps", &target_->data_deps()))
+      return false;
+  }
+
+  return true;
+}
+
+bool TargetGenerator::FillMetadata() {
+  // Need to get a mutable value to mark all values in the scope as used. This
+  // cannot be done on a const Scope.
+  Value* value = scope_->GetMutableValue(variables::kMetadata,
+                                         Scope::SEARCH_CURRENT, true);
+
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::SCOPE, err_))
+    return false;
+
+  Scope* scope_value = value->scope_value();
+
+  scope_value->GetCurrentScopeValues(&target_->metadata().contents());
+  scope_value->MarkAllUsed();
+
+  // Metadata values should always hold lists of Values, such that they can be
+  // collected and concatenated. Any additional specific type verification is
+  // done at walk time.
+  for (const auto& iter : target_->metadata().contents()) {
+    if (!iter.second.VerifyTypeIs(Value::LIST, err_))
+      return false;
+  }
+
+  target_->metadata().set_source_dir(scope_->GetSourceDir());
+  target_->metadata().set_origin(value->origin());
+  return true;
+}
+
+bool TargetGenerator::FillTestonly() {
+  const Value* value = scope_->GetValue(variables::kTestonly, true);
+  if (value) {
+    if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+      return false;
+    target_->set_testonly(value->boolean_value());
+  }
+  return true;
+}
+
+bool TargetGenerator::FillAssertNoDeps() {
+  const Value* value = scope_->GetValue(variables::kAssertNoDeps, true);
+  if (value) {
+    return ExtractListOfLabelPatterns(scope_->settings()->build_settings(),
+                                      *value, scope_->GetSourceDir(),
+                                      &target_->assert_no_deps(), err_);
+  }
+  return true;
+}
+
+bool TargetGenerator::FillOutputs(bool allow_substitutions) {
+  const Value* value = scope_->GetValue(variables::kOutputs, true);
+  if (!value)
+    return true;
+
+  SubstitutionList& outputs = target_->action_values().outputs();
+  if (!outputs.Parse(*value, err_))
+    return false;
+
+  if (!allow_substitutions) {
+    // Verify no substitutions were actually used.
+    if (!outputs.required_types().empty()) {
+      *err_ =
+          Err(*value, "Source expansions not allowed here.",
+              "The outputs of this target used source {{expansions}} but this "
+              "target type\ndoesn't support them. Just express the outputs "
+              "literally.");
+      return false;
+    }
+  }
+
+  // Check the substitutions used are valid for this purpose.
+  if (!EnsureValidSubstitutions(outputs.required_types(),
+                                &IsValidSourceSubstitution, value->origin(),
+                                err_))
+    return false;
+
+  // Validate that outputs are in the output dir.
+  CHECK(outputs.list().size() == value->list_value().size());
+  for (size_t i = 0; i < outputs.list().size(); i++) {
+    if (!EnsureSubstitutionIsInOutputDir(outputs.list()[i],
+                                         value->list_value()[i]))
+      return false;
+  }
+  return true;
+}
+
+bool TargetGenerator::FillCheckIncludes() {
+  const Value* value = scope_->GetValue(variables::kCheckIncludes, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+    return false;
+  target_->set_check_includes(value->boolean_value());
+  return true;
+}
+
+bool TargetGenerator::FillOutputExtension() {
+  const Value* value = scope_->GetValue(variables::kOutputExtension, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+  target_->set_output_extension(value->string_value());
+  return true;
+}
+
+bool TargetGenerator::EnsureSubstitutionIsInOutputDir(
+    const SubstitutionPattern& pattern,
+    const Value& original_value) {
+  if (pattern.ranges().empty()) {
+    // Pattern is empty, error out (this prevents weirdness below).
+    *err_ = Err(original_value, "This has an empty value in it.");
+    return false;
+  }
+
+  if (pattern.ranges()[0].type == &SubstitutionLiteral) {
+    // If the first thing is a literal, it must start with the output dir.
+    if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(),
+                                   pattern.ranges()[0].literal,
+                                   original_value.origin(), err_))
+      return false;
+  } else {
+    // Otherwise, the first subrange must be a pattern that expands to
+    // something in the output directory.
+    if (!SubstitutionIsInOutputDir(pattern.ranges()[0].type)) {
+      *err_ =
+          Err(original_value, "File is not inside output directory.",
+              "The given file should be in the output directory. Normally you\n"
+              "would specify\n\"$target_out_dir/foo\" or "
+              "\"{{source_gen_dir}}/foo\".");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool TargetGenerator::FillGenericConfigs(const char* var_name,
+                                         UniqueVector<LabelConfigPair>* dest) {
+  const Value* value = scope_->GetValue(var_name, true);
+  if (value) {
+    ExtractListOfUniqueLabels(scope_->settings()->build_settings(), *value,
+                              scope_->GetSourceDir(),
+                              ToolchainLabelForScope(scope_), dest, err_);
+  }
+  return !err_->has_error();
+}
+
+bool TargetGenerator::FillGenericDeps(const char* var_name,
+                                      LabelTargetVector* dest) {
+  const Value* value = scope_->GetValue(var_name, true);
+  if (value) {
+    ExtractListOfLabels(scope_->settings()->build_settings(), *value,
+                        scope_->GetSourceDir(), ToolchainLabelForScope(scope_),
+                        dest, err_);
+  }
+  return !err_->has_error();
+}
+
+bool TargetGenerator::FillWriteRuntimeDeps() {
+  const Value* value = scope_->GetValue(variables::kWriteRuntimeDeps, true);
+  if (!value)
+    return true;
+
+  // Compute the file name and make sure it's in the output dir.
+  SourceFile source_file = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, GetBuildSettings()->root_path_utf8());
+  if (err_->has_error())
+    return false;
+  if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(),
+                                 source_file.value(), value->origin(), err_))
+    return false;
+  OutputFile output_file(GetBuildSettings(), source_file);
+  target_->set_write_runtime_deps_output(output_file);
+
+  return true;
+}
diff --git a/src/gn/target_generator.h b/src/gn/target_generator.h
new file mode 100644 (file)
index 0000000..2053196
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TARGET_GENERATOR_H_
+#define TOOLS_GN_TARGET_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/label_ptr.h"
+#include "gn/unique_vector.h"
+
+class BuildSettings;
+class Err;
+class FunctionCallNode;
+class Scope;
+class SubstitutionPattern;
+class Value;
+
+// Fills the variables in a Target object from a Scope (the result of a script
+// execution). Target-type-specific derivations of this class will be used
+// for each different type of function call. This class implements the common
+// behavior.
+class TargetGenerator {
+ public:
+  TargetGenerator(Target* target,
+                  Scope* scope,
+                  const FunctionCallNode* function_call,
+                  Err* err);
+  virtual ~TargetGenerator();
+
+  void Run();
+
+  // The function call is the parse tree node that invoked the target.
+  // err() will be set on failure.
+  static void GenerateTarget(Scope* scope,
+                             const FunctionCallNode* function_call,
+                             const std::vector<Value>& args,
+                             const std::string& output_type,
+                             Err* err);
+
+ protected:
+  // Derived classes implement this to do type-specific generation.
+  virtual void DoRun() = 0;
+
+  const BuildSettings* GetBuildSettings() const;
+
+  virtual bool FillSources();
+  bool FillPublic();
+  bool FillConfigs();
+  bool FillOutputs(bool allow_substitutions);
+  bool FillCheckIncludes();
+  bool FillOutputExtension();
+
+  // Rrturns true if the given pattern will expand to a file in the output
+  // directory. If not, returns false and sets the error, blaming the given
+  // Value.
+  bool EnsureSubstitutionIsInOutputDir(const SubstitutionPattern& pattern,
+                                       const Value& original_value);
+
+  Target* target_;
+  Scope* scope_;
+  const FunctionCallNode* function_call_;
+  Err* err_;
+
+ private:
+  bool FillDependentConfigs();  // Includes all types of dependent configs.
+  bool FillData();
+  bool FillDependencies();  // Includes data dependencies.
+  bool FillMetadata();
+  bool FillTestonly();
+  bool FillAssertNoDeps();
+  bool FillWriteRuntimeDeps();
+
+  // Reads configs/deps from the given var name, and uses the given setting on
+  // the target to save them.
+  bool FillGenericConfigs(const char* var_name,
+                          UniqueVector<LabelConfigPair>* dest);
+  bool FillGenericDeps(const char* var_name, LabelTargetVector* dest);
+
+  DISALLOW_COPY_AND_ASSIGN(TargetGenerator);
+};
+
+#endif  // TOOLS_GN_TARGET_GENERATOR_H_
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
new file mode 100644 (file)
index 0000000..792c530
--- /dev/null
@@ -0,0 +1,1539 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/target.h"
+
+#include <memory>
+#include <utility>
+
+#include "gn/build_settings.h"
+#include "gn/config.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "gn/toolchain.h"
+#include "util/test/test.h"
+
+namespace {
+
+// Asserts that the current global scheduler has a single unknown generated
+// file with the given name from the given target.
+void AssertSchedulerHasOneUnknownFileMatching(const Target* target,
+                                              const SourceFile& file) {
+  auto unknown = g_scheduler->GetUnknownGeneratedInputs();
+  ASSERT_EQ(1u, unknown.size());  // Should be one unknown file.
+  auto found = unknown.find(file);
+  ASSERT_TRUE(found != unknown.end()) << file.value();
+  EXPECT_TRUE(target == found->second)
+      << "Target doesn't match. Expected\n  "
+      << target->label().GetUserVisibleName(false) << "\nBut got\n  "
+      << found->second->label().GetUserVisibleName(false);
+}
+
+}  // namespace
+
+using TargetTest = TestWithScheduler;
+
+// Tests that lib[_dir]s are inherited across deps boundaries for static
+// libraries but not executables.
+TEST_F(TargetTest, LibInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  const LibFile lib("foo");
+  const SourceDir libdir("/foo_dir/");
+
+  // Leaf target with ldflags set.
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
+  z.config_values().libs().push_back(lib);
+  z.config_values().lib_dirs().push_back(libdir);
+  ASSERT_TRUE(z.OnResolved(&err));
+
+  // All lib[_dir]s should be set when target is resolved.
+  ASSERT_EQ(1u, z.all_libs().size());
+  EXPECT_EQ(lib, z.all_libs()[0]);
+  ASSERT_EQ(1u, z.all_lib_dirs().size());
+  EXPECT_EQ(libdir, z.all_lib_dirs()[0]);
+
+  // Shared library target should inherit the libs from the static library
+  // and its own. Its own flag should be before the inherited one.
+  const LibFile second_lib("bar");
+  const SourceDir second_libdir("/bar_dir/");
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
+  shared.config_values().libs().push_back(second_lib);
+  shared.config_values().lib_dirs().push_back(second_libdir);
+  shared.private_deps().push_back(LabelTargetPair(&z));
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  ASSERT_EQ(2u, shared.all_libs().size());
+  EXPECT_EQ(second_lib, shared.all_libs()[0]);
+  EXPECT_EQ(lib, shared.all_libs()[1]);
+  ASSERT_EQ(2u, shared.all_lib_dirs().size());
+  EXPECT_EQ(second_libdir, shared.all_lib_dirs()[0]);
+  EXPECT_EQ(libdir, shared.all_lib_dirs()[1]);
+
+  // Executable target shouldn't get either by depending on shared.
+  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
+  exec.private_deps().push_back(LabelTargetPair(&shared));
+  ASSERT_TRUE(exec.OnResolved(&err));
+  EXPECT_EQ(0u, exec.all_libs().size());
+  EXPECT_EQ(0u, exec.all_lib_dirs().size());
+}
+
+// Tests that framework[_dir]s are inherited across deps boundaries for static
+// libraries but not executables.
+TEST_F(TargetTest, FrameworkInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  const std::string framework("Foo.framework");
+  const SourceDir frameworkdir("//out/foo/");
+
+  // Leaf target with ldflags set.
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
+  z.config_values().frameworks().push_back(framework);
+  z.config_values().framework_dirs().push_back(frameworkdir);
+  ASSERT_TRUE(z.OnResolved(&err));
+
+  // All framework[_dir]s should be set when target is resolved.
+  ASSERT_EQ(1u, z.all_frameworks().size());
+  EXPECT_EQ(framework, z.all_frameworks()[0]);
+  ASSERT_EQ(1u, z.all_framework_dirs().size());
+  EXPECT_EQ(frameworkdir, z.all_framework_dirs()[0]);
+
+  // Shared library target should inherit the libs from the static library
+  // and its own. Its own flag should be before the inherited one.
+  const std::string second_framework("Bar.framework");
+  const SourceDir second_frameworkdir("//out/bar/");
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
+  shared.config_values().frameworks().push_back(second_framework);
+  shared.config_values().framework_dirs().push_back(second_frameworkdir);
+  shared.private_deps().push_back(LabelTargetPair(&z));
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  ASSERT_EQ(2u, shared.all_frameworks().size());
+  EXPECT_EQ(second_framework, shared.all_frameworks()[0]);
+  EXPECT_EQ(framework, shared.all_frameworks()[1]);
+  ASSERT_EQ(2u, shared.all_framework_dirs().size());
+  EXPECT_EQ(second_frameworkdir, shared.all_framework_dirs()[0]);
+  EXPECT_EQ(frameworkdir, shared.all_framework_dirs()[1]);
+
+  // Executable target shouldn't get either by depending on shared.
+  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
+  exec.private_deps().push_back(LabelTargetPair(&shared));
+  ASSERT_TRUE(exec.OnResolved(&err));
+  EXPECT_EQ(0u, exec.all_frameworks().size());
+  EXPECT_EQ(0u, exec.all_framework_dirs().size());
+}
+
+// Test all_dependent_configs and public_config inheritance.
+TEST_F(TargetTest, DependentConfigs) {
+  TestWithScope setup;
+  Err err;
+
+  // Set up a dependency chain of a -> b -> c
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+
+  // Normal non-inherited config.
+  Config config(setup.settings(), Label(SourceDir("//foo/"), "config"));
+  config.visibility().SetPublic();
+  ASSERT_TRUE(config.OnResolved(&err));
+  c.configs().push_back(LabelConfigPair(&config));
+
+  // All dependent config.
+  Config all(setup.settings(), Label(SourceDir("//foo/"), "all"));
+  all.visibility().SetPublic();
+  ASSERT_TRUE(all.OnResolved(&err));
+  c.all_dependent_configs().push_back(LabelConfigPair(&all));
+
+  // Direct dependent config.
+  Config direct(setup.settings(), Label(SourceDir("//foo/"), "direct"));
+  direct.visibility().SetPublic();
+  ASSERT_TRUE(direct.OnResolved(&err));
+  c.public_configs().push_back(LabelConfigPair(&direct));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have gotten both dependent configs from C.
+  ASSERT_EQ(2u, b.configs().size());
+  EXPECT_EQ(&all, b.configs()[0].ptr);
+  EXPECT_EQ(&direct, b.configs()[1].ptr);
+  ASSERT_EQ(1u, b.all_dependent_configs().size());
+  EXPECT_EQ(&all, b.all_dependent_configs()[0].ptr);
+
+  // A should have just gotten the "all" dependent config from C.
+  ASSERT_EQ(1u, a.configs().size());
+  EXPECT_EQ(&all, a.configs()[0].ptr);
+  EXPECT_EQ(&all, a.all_dependent_configs()[0].ptr);
+
+  // Making an an alternate A and B with B forwarding the direct dependents.
+  TestTarget a_fwd(setup, "//foo:a_fwd", Target::EXECUTABLE);
+  TestTarget b_fwd(setup, "//foo:b_fwd", Target::STATIC_LIBRARY);
+  a_fwd.private_deps().push_back(LabelTargetPair(&b_fwd));
+  b_fwd.private_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(b_fwd.OnResolved(&err));
+  ASSERT_TRUE(a_fwd.OnResolved(&err));
+
+  // A_fwd should now have both configs.
+  ASSERT_EQ(1u, a_fwd.configs().size());
+  EXPECT_EQ(&all, a_fwd.configs()[0].ptr);
+  ASSERT_EQ(1u, a_fwd.all_dependent_configs().size());
+  EXPECT_EQ(&all, a_fwd.all_dependent_configs()[0].ptr);
+}
+
+// Tests that dependent configs don't propagate between toolchains.
+TEST_F(TargetTest, NoDependentConfigsBetweenToolchains) {
+  TestWithScope setup;
+  Err err;
+
+  // Create another toolchain.
+  Toolchain other_toolchain(setup.settings(),
+                            Label(SourceDir("//other/"), "toolchain"));
+  TestWithScope::SetupToolchain(&other_toolchain);
+
+  // Set up a dependency chain of |a| -> |b| -> |c| where |a| has a different
+  // toolchain.
+  Target a(setup.settings(),
+           Label(SourceDir("//foo/"), "a", other_toolchain.label().dir(),
+                 other_toolchain.label().name()));
+  a.set_output_type(Target::EXECUTABLE);
+  EXPECT_TRUE(a.SetToolchain(&other_toolchain, &err));
+  TestTarget b(setup, "//foo:b", Target::EXECUTABLE);
+  TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+
+  // All dependent config.
+  Config all_dependent(setup.settings(), Label(SourceDir("//foo/"), "all"));
+  all_dependent.visibility().SetPublic();
+  ASSERT_TRUE(all_dependent.OnResolved(&err));
+  c.all_dependent_configs().push_back(LabelConfigPair(&all_dependent));
+
+  // Public config.
+  Config public_config(setup.settings(), Label(SourceDir("//foo/"), "public"));
+  public_config.visibility().SetPublic();
+  ASSERT_TRUE(public_config.OnResolved(&err));
+  c.public_configs().push_back(LabelConfigPair(&public_config));
+
+  // Another public config.
+  Config public_config2(setup.settings(),
+                        Label(SourceDir("//foo/"), "public2"));
+  public_config2.visibility().SetPublic();
+  ASSERT_TRUE(public_config2.OnResolved(&err));
+  b.public_configs().push_back(LabelConfigPair(&public_config2));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have gotten the configs from C.
+  ASSERT_EQ(3u, b.configs().size());
+  EXPECT_EQ(&public_config2, b.configs()[0].ptr);
+  EXPECT_EQ(&all_dependent, b.configs()[1].ptr);
+  EXPECT_EQ(&public_config, b.configs()[2].ptr);
+  ASSERT_EQ(1u, b.all_dependent_configs().size());
+  EXPECT_EQ(&all_dependent, b.all_dependent_configs()[0].ptr);
+
+  // A should not have gotten any configs from B or C.
+  ASSERT_EQ(0u, a.configs().size());
+  ASSERT_EQ(0u, a.all_dependent_configs().size());
+}
+
+// Tests that dependent configs propagate between toolchains if
+// propagates_configs is set.
+TEST_F(TargetTest, DependentConfigsBetweenToolchainsWhenSet) {
+  TestWithScope setup;
+  Err err;
+
+  // Create another toolchain.
+  Toolchain other_toolchain(setup.settings(),
+                            Label(SourceDir("//other/"), "toolchain"));
+  TestWithScope::SetupToolchain(&other_toolchain);
+  other_toolchain.set_propagates_configs(true);
+
+  // Set up a dependency chain of |a| -> |b| where |b| has a different
+  // toolchain (with propagate_configs set).
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  Target b(setup.settings(),
+           Label(SourceDir("//foo/"), "b", other_toolchain.label().dir(),
+                 other_toolchain.label().name()));
+  b.visibility().SetPublic();
+  b.set_output_type(Target::SHARED_LIBRARY);
+  EXPECT_TRUE(b.SetToolchain(&other_toolchain, &err));
+  a.private_deps().push_back(LabelTargetPair(&b));
+
+  // All dependent config.
+  Config all_dependent(setup.settings(), Label(SourceDir("//foo/"), "all"));
+  all_dependent.visibility().SetPublic();
+  ASSERT_TRUE(all_dependent.OnResolved(&err));
+  b.all_dependent_configs().push_back(LabelConfigPair(&all_dependent));
+
+  // Public config.
+  Config public_config(setup.settings(), Label(SourceDir("//foo/"), "public"));
+  public_config.visibility().SetPublic();
+  ASSERT_TRUE(public_config.OnResolved(&err));
+  b.public_configs().push_back(LabelConfigPair(&public_config));
+
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // A should have gotten the configs from B.
+  ASSERT_EQ(2u, a.configs().size());
+  EXPECT_EQ(&all_dependent, a.configs()[0].ptr);
+  EXPECT_EQ(&public_config, a.configs()[1].ptr);
+  ASSERT_EQ(1u, a.all_dependent_configs().size());
+  EXPECT_EQ(&all_dependent, a.all_dependent_configs()[0].ptr);
+}
+
+TEST_F(TargetTest, InheritLibs) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (shared lib) -> C (static lib) -> D (source set)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::SHARED_LIBRARY);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  TestTarget d(setup, "//foo:d", Target::SOURCE_SET);
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+  c.private_deps().push_back(LabelTargetPair(&d));
+
+  ASSERT_TRUE(d.OnResolved(&err));
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // C should have D in its inherited libs.
+  std::vector<const Target*> c_inherited = c.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, c_inherited.size());
+  EXPECT_EQ(&d, c_inherited[0]);
+
+  // B should have C and D in its inherited libs.
+  std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+  ASSERT_EQ(2u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0]);
+  EXPECT_EQ(&d, b_inherited[1]);
+
+  // A should have B in its inherited libs, but not any others (the shared
+  // library will include the static library and source set).
+  std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0]);
+}
+
+TEST_F(TargetTest, InheritCompleteStaticLib) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (source set)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+
+  const LibFile lib("foo");
+  const SourceDir lib_dir("/foo_dir/");
+  TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+  c.config_values().libs().push_back(lib);
+  c.config_values().lib_dirs().push_back(lib_dir);
+
+  a.public_deps().push_back(LabelTargetPair(&b));
+  b.public_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have C in its inherited libs.
+  std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0]);
+
+  // A should have B in its inherited libs, but not any others (the complete
+  // static library will include the source set).
+  std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0]);
+
+  // A should inherit the libs and lib_dirs from the C.
+  ASSERT_EQ(1u, a.all_libs().size());
+  EXPECT_EQ(lib, a.all_libs()[0]);
+  ASSERT_EQ(1u, a.all_lib_dirs().size());
+  EXPECT_EQ(lib_dir, a.all_lib_dirs()[0]);
+}
+
+TEST_F(TargetTest, InheritCompleteStaticLibStaticLibDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (static lib)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  a.public_deps().push_back(LabelTargetPair(&b));
+  b.public_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have C in its inherited libs.
+  std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0]);
+
+  // A should have B in its inherited libs, but not any others (the complete
+  // static library will include the static library).
+  std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0]);
+}
+
+TEST_F(TargetTest, InheritCompleteStaticLibInheritedCompleteStaticLibDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (complete static lib)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  c.set_complete_static_lib(true);
+
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have C in its inherited libs.
+  std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0]);
+
+  // A should have B and C in its inherited libs.
+  std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+  ASSERT_EQ(2u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0]);
+  EXPECT_EQ(&c, a_inherited[1]);
+}
+
+TEST_F(TargetTest, NoActionDepPropgation) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (exe) -> B (action) -> C (source_set)
+  {
+    TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+    TestTarget b(setup, "//foo:b", Target::ACTION);
+    TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+
+    a.private_deps().push_back(LabelTargetPair(&b));
+    b.private_deps().push_back(LabelTargetPair(&c));
+
+    ASSERT_TRUE(c.OnResolved(&err));
+    ASSERT_TRUE(b.OnResolved(&err));
+    ASSERT_TRUE(a.OnResolved(&err));
+
+    // The executable should not have inherited the source set across the
+    // action.
+    std::vector<const Target*> libs = a.inherited_libraries().GetOrdered();
+    ASSERT_TRUE(libs.empty());
+  }
+}
+
+TEST_F(TargetTest, GetComputedOutputName) {
+  TestWithScope setup;
+  Err err;
+
+  // Basic target with no prefix (executable type tool in the TestWithScope has
+  // no prefix) or output name.
+  TestTarget basic(setup, "//foo:bar", Target::EXECUTABLE);
+  ASSERT_TRUE(basic.OnResolved(&err));
+  EXPECT_EQ("bar", basic.GetComputedOutputName());
+
+  // Target with no prefix but an output name.
+  TestTarget with_name(setup, "//foo:bar", Target::EXECUTABLE);
+  with_name.set_output_name("myoutput");
+  ASSERT_TRUE(with_name.OnResolved(&err));
+  EXPECT_EQ("myoutput", with_name.GetComputedOutputName());
+
+  // Target with a "lib" prefix (the static library tool in the TestWithScope
+  // should specify a "lib" output prefix).
+  TestTarget with_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  ASSERT_TRUE(with_prefix.OnResolved(&err));
+  EXPECT_EQ("libbar", with_prefix.GetComputedOutputName());
+
+  // Target with a "lib" prefix that already has it applied. The prefix should
+  // not duplicate something already in the target name.
+  TestTarget dup_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  dup_prefix.set_output_name("libbar");
+  ASSERT_TRUE(dup_prefix.OnResolved(&err));
+  EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName());
+
+  // Target with an output prefix override should not have a prefix.
+  TestTarget override_prefix(setup, "//foo:bar", Target::SHARED_LIBRARY);
+  override_prefix.set_output_prefix_override(true);
+  ASSERT_TRUE(dup_prefix.OnResolved(&err));
+  EXPECT_EQ("bar", override_prefix.GetComputedOutputName());
+}
+
+// Test visibility failure case.
+TEST_F(TargetTest, VisibilityFails) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget b(setup, "//private:b", Target::STATIC_LIBRARY);
+  b.visibility().SetPrivate(b.label().dir());
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  // Make a target depending on "b". The dependency must have an origin to mark
+  // it as user-set so we check visibility. This check should fail.
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
+  a.private_deps().push_back(LabelTargetPair(&b));
+  IdentifierNode origin;  // Dummy origin.
+  a.private_deps()[0].origin = &origin;
+  ASSERT_FALSE(a.OnResolved(&err));
+}
+
+// Test visibility with a single data_dep.
+TEST_F(TargetTest, VisibilityDatadeps) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget b(setup, "//public:b", Target::STATIC_LIBRARY);
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  // Make a target depending on "b". The dependency must have an origin to mark
+  // it as user-set so we check visibility. This check should fail.
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
+  a.data_deps().push_back(LabelTargetPair(&b));
+  IdentifierNode origin;  // Dummy origin.
+  a.data_deps()[0].origin = &origin;
+  ASSERT_TRUE(a.OnResolved(&err)) << err.help_text();
+}
+
+// Tests that A -> Group -> B where the group is visible from A but B isn't,
+// passes visibility even though the group's deps get expanded into A.
+TEST_F(TargetTest, VisibilityGroup) {
+  TestWithScope setup;
+  Err err;
+
+  IdentifierNode origin;  // Dummy origin.
+
+  // B has private visibility. This lets the group see it since the group is in
+  // the same directory.
+  TestTarget b(setup, "//private:b", Target::STATIC_LIBRARY);
+  b.visibility().SetPrivate(b.label().dir());
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  // The group has public visibility and depends on b.
+  TestTarget g(setup, "//public:g", Target::GROUP);
+  g.private_deps().push_back(LabelTargetPair(&b));
+  g.private_deps()[0].origin = &origin;
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  // Make a target depending on "g". This should succeed.
+  TestTarget a(setup, "//app:a", Target::EXECUTABLE);
+  a.private_deps().push_back(LabelTargetPair(&g));
+  a.private_deps()[0].origin = &origin;
+  ASSERT_TRUE(a.OnResolved(&err));
+}
+
+// Verifies that only testonly targets can depend on other testonly targets.
+// Many of the above dependency checking cases covered the non-testonly
+// case.
+TEST_F(TargetTest, Testonly) {
+  TestWithScope setup;
+  Err err;
+
+  // "testlib" is a test-only library.
+  TestTarget testlib(setup, "//test:testlib", Target::STATIC_LIBRARY);
+  testlib.set_testonly(true);
+  ASSERT_TRUE(testlib.OnResolved(&err));
+
+  // "test" is a test-only executable depending on testlib, this is OK.
+  TestTarget test(setup, "//test:test", Target::EXECUTABLE);
+  test.set_testonly(true);
+  test.private_deps().push_back(LabelTargetPair(&testlib));
+  ASSERT_TRUE(test.OnResolved(&err));
+
+  // "product" is a non-test depending on testlib. This should fail.
+  TestTarget product(setup, "//app:product", Target::EXECUTABLE);
+  product.set_testonly(false);
+  product.private_deps().push_back(LabelTargetPair(&testlib));
+  ASSERT_FALSE(product.OnResolved(&err));
+}
+
+TEST_F(TargetTest, PublicConfigs) {
+  TestWithScope setup;
+  Err err;
+
+  Label pub_config_label(SourceDir("//a/"), "pubconfig");
+  Config pub_config(setup.settings(), pub_config_label);
+  pub_config.visibility().SetPublic();
+  LibFile lib_name("testlib");
+  pub_config.own_values().libs().push_back(lib_name);
+  ASSERT_TRUE(pub_config.OnResolved(&err));
+
+  // This is the destination target that has a public config.
+  TestTarget dest(setup, "//a:a", Target::SOURCE_SET);
+  dest.public_configs().push_back(LabelConfigPair(&pub_config));
+  ASSERT_TRUE(dest.OnResolved(&err));
+
+  // This target has a public dependency on dest.
+  TestTarget pub(setup, "//a:pub", Target::SOURCE_SET);
+  pub.public_deps().push_back(LabelTargetPair(&dest));
+  ASSERT_TRUE(pub.OnResolved(&err));
+
+  // Depending on the target with the public dependency should forward dest's
+  // to the current target.
+  TestTarget dep_on_pub(setup, "//a:dop", Target::SOURCE_SET);
+  dep_on_pub.private_deps().push_back(LabelTargetPair(&pub));
+  ASSERT_TRUE(dep_on_pub.OnResolved(&err));
+  ASSERT_EQ(1u, dep_on_pub.configs().size());
+  EXPECT_EQ(&pub_config, dep_on_pub.configs()[0].ptr);
+
+  // Libs have special handling, check that they were forwarded from the
+  // public config to all_libs.
+  ASSERT_EQ(1u, dep_on_pub.all_libs().size());
+  ASSERT_EQ(lib_name, dep_on_pub.all_libs()[0]);
+
+  // This target has a private dependency on dest for forwards configs.
+  TestTarget forward(setup, "//a:f", Target::SOURCE_SET);
+  forward.private_deps().push_back(LabelTargetPair(&dest));
+  ASSERT_TRUE(forward.OnResolved(&err));
+}
+
+// Tests that configs are ordered properly between local and pulled ones.
+TEST_F(TargetTest, ConfigOrdering) {
+  TestWithScope setup;
+  Err err;
+
+  // Make Dep1. It has all_dependent_configs and public_configs.
+  TestTarget dep1(setup, "//:dep1", Target::SOURCE_SET);
+  Label dep1_all_config_label(SourceDir("//"), "dep1_all_config");
+  Config dep1_all_config(setup.settings(), dep1_all_config_label);
+  dep1_all_config.visibility().SetPublic();
+  ASSERT_TRUE(dep1_all_config.OnResolved(&err));
+  dep1.all_dependent_configs().push_back(LabelConfigPair(&dep1_all_config));
+
+  Label dep1_public_config_label(SourceDir("//"), "dep1_public_config");
+  Config dep1_public_config(setup.settings(), dep1_public_config_label);
+  dep1_public_config.visibility().SetPublic();
+  ASSERT_TRUE(dep1_public_config.OnResolved(&err));
+  dep1.public_configs().push_back(LabelConfigPair(&dep1_public_config));
+  ASSERT_TRUE(dep1.OnResolved(&err));
+
+  // Make Dep2 with the same structure.
+  TestTarget dep2(setup, "//:dep2", Target::SOURCE_SET);
+  Label dep2_all_config_label(SourceDir("//"), "dep2_all_config");
+  Config dep2_all_config(setup.settings(), dep2_all_config_label);
+  dep2_all_config.visibility().SetPublic();
+  ASSERT_TRUE(dep2_all_config.OnResolved(&err));
+  dep2.all_dependent_configs().push_back(LabelConfigPair(&dep2_all_config));
+
+  Label dep2_public_config_label(SourceDir("//"), "dep2_public_config");
+  Config dep2_public_config(setup.settings(), dep2_public_config_label);
+  dep2_public_config.visibility().SetPublic();
+  ASSERT_TRUE(dep2_public_config.OnResolved(&err));
+  dep2.public_configs().push_back(LabelConfigPair(&dep2_public_config));
+  ASSERT_TRUE(dep2.OnResolved(&err));
+
+  // This target depends on both previous targets.
+  TestTarget target(setup, "//:foo", Target::SOURCE_SET);
+  target.private_deps().push_back(LabelTargetPair(&dep1));
+  target.private_deps().push_back(LabelTargetPair(&dep2));
+
+  // It also has a private and public config.
+  Label public_config_label(SourceDir("//"), "public");
+  Config public_config(setup.settings(), public_config_label);
+  public_config.visibility().SetPublic();
+  ASSERT_TRUE(public_config.OnResolved(&err));
+  target.public_configs().push_back(LabelConfigPair(&public_config));
+
+  Label private_config_label(SourceDir("//"), "private");
+  Config private_config(setup.settings(), private_config_label);
+  private_config.visibility().SetPublic();
+  ASSERT_TRUE(private_config.OnResolved(&err));
+  target.configs().push_back(LabelConfigPair(&private_config));
+
+  // Resolve to get the computed list of configs applying.
+  ASSERT_TRUE(target.OnResolved(&err));
+  const auto& computed = target.configs();
+
+  // Order should be:
+  // 1. local private
+  // 2. local public
+  // 3. inherited all dependent
+  // 4. inherited public
+  ASSERT_EQ(6u, computed.size());
+  EXPECT_EQ(private_config_label, computed[0].label);
+  EXPECT_EQ(public_config_label, computed[1].label);
+  EXPECT_EQ(dep1_all_config_label, computed[2].label);
+  EXPECT_EQ(dep2_all_config_label, computed[3].label);
+  EXPECT_EQ(dep1_public_config_label, computed[4].label);
+  EXPECT_EQ(dep2_public_config_label, computed[5].label);
+}
+
+// Tests that different link/depend outputs work for solink tools.
+TEST_F(TargetTest, LinkAndDepOutputs) {
+  TestWithScope setup;
+  Err err;
+
+  Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+  std::unique_ptr<Tool> solink = Tool::CreateTool(CTool::kCToolSolink);
+  CTool* solink_tool = solink->AsC();
+  solink_tool->set_output_prefix("lib");
+  solink_tool->set_default_output_extension(".so");
+
+  const char kLinkPattern[] =
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}";
+  SubstitutionPattern link_output =
+      SubstitutionPattern::MakeForTest(kLinkPattern);
+  solink_tool->set_link_output(link_output);
+
+  const char kDependPattern[] =
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.TOC";
+  SubstitutionPattern depend_output =
+      SubstitutionPattern::MakeForTest(kDependPattern);
+  solink_tool->set_depend_output(depend_output);
+
+  solink_tool->set_outputs(
+      SubstitutionList::MakeForTest(kLinkPattern, kDependPattern));
+
+  toolchain.SetTool(std::move(solink));
+
+  Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.SetToolchain(&toolchain);
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  EXPECT_EQ("./liba.so", target.link_output_file().value());
+  EXPECT_EQ("./liba.so.TOC", target.dependency_output_file().value());
+
+  ASSERT_EQ(1u, target.runtime_outputs().size());
+  EXPECT_EQ("./liba.so", target.runtime_outputs()[0].value());
+}
+
+// Tests that runtime_outputs works without an explicit link_output for
+// solink tools.
+//
+// Also tests GetOutputsAsSourceFiles() for binaries (the setup is the same).
+TEST_F(TargetTest, RuntimeOuputs) {
+  TestWithScope setup;
+  Err err;
+
+  Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+  std::unique_ptr<Tool> solink = Tool::CreateTool(CTool::kCToolSolink);
+  CTool* solink_tool = solink->AsC();
+  solink_tool->set_output_prefix("");
+  solink_tool->set_default_output_extension(".dll");
+
+  // Say the linker makes a DLL< an import library, and a symbol file we want
+  // to treat as a runtime output.
+  const char kLibPattern[] =
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.lib";
+  const char kDllPattern[] =
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}";
+  const char kPdbPattern[] = "{{root_out_dir}}/{{target_output_name}}.pdb";
+  SubstitutionPattern pdb_pattern =
+      SubstitutionPattern::MakeForTest(kPdbPattern);
+
+  solink_tool->set_outputs(
+      SubstitutionList::MakeForTest(kLibPattern, kDllPattern, kPdbPattern));
+
+  // Say we only want the DLL and symbol file treaded as runtime outputs.
+  solink_tool->set_runtime_outputs(
+      SubstitutionList::MakeForTest(kDllPattern, kPdbPattern));
+
+  toolchain.SetTool(std::move(solink));
+
+  Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.SetToolchain(&toolchain);
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  EXPECT_EQ("./a.dll.lib", target.link_output_file().value());
+  EXPECT_EQ("./a.dll.lib", target.dependency_output_file().value());
+
+  ASSERT_EQ(2u, target.runtime_outputs().size());
+  EXPECT_EQ("./a.dll", target.runtime_outputs()[0].value());
+  EXPECT_EQ("./a.pdb", target.runtime_outputs()[1].value());
+
+  // Test GetOutputsAsSourceFiles().
+  std::vector<SourceFile> computed_outputs;
+  EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+                                             &computed_outputs, &err));
+  ASSERT_EQ(3u, computed_outputs.size());
+  EXPECT_EQ("//out/Debug/a.dll.lib", computed_outputs[0].value());
+  EXPECT_EQ("//out/Debug/a.dll", computed_outputs[1].value());
+  EXPECT_EQ("//out/Debug/a.pdb", computed_outputs[2].value());
+}
+
+// Tests Target::GetOutputFilesForSource for binary targets (these require a
+// tool definition). Also tests GetOutputsAsSourceFiles() for source sets.
+TEST_F(TargetTest, GetOutputFilesForSource_Binary) {
+  TestWithScope setup;
+
+  Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolCxx);
+  CTool* cxx = tool->AsC();
+  cxx->set_outputs(SubstitutionList::MakeForTest("{{source_file_part}}.o"));
+  toolchain.SetTool(std::move(tool));
+
+  Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.SetToolchain(&toolchain);
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const char* computed_tool_type = nullptr;
+  std::vector<OutputFile> output;
+  bool result = target.GetOutputFilesForSource(SourceFile("//source/input.cc"),
+                                               &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+  EXPECT_EQ(std::string("cxx"), std::string(computed_tool_type));
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  ASSERT_EQ(1u, output.size());
+  EXPECT_EQ("input.cc.o", output[0].value()) << output[0].value();
+
+  // Test GetOutputsAsSourceFiles(). Since this is a source set it should give a
+  // stamp file.
+  std::vector<SourceFile> computed_outputs;
+  EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+                                             &computed_outputs, &err));
+  ASSERT_EQ(1u, computed_outputs.size());
+  EXPECT_EQ("//out/Debug/obj/a/a.stamp", computed_outputs[0].value());
+}
+
+// Tests Target::GetOutputFilesForSource for action_foreach targets (these, like
+// copy targets, apply a pattern to the source file). Also tests
+// GetOutputsAsSourceFiles() for action_foreach().
+TEST_F(TargetTest, GetOutputFilesForSource_ActionForEach) {
+  TestWithScope setup;
+
+  TestTarget target(setup, "//a:a", Target::ACTION_FOREACH);
+  target.sources().push_back(SourceFile("//a/source_file1.txt"));
+  target.sources().push_back(SourceFile("//a/source_file2.txt"));
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/{{source_file_part}}.one",
+                                    "//out/Debug/{{source_file_part}}.two");
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const char* computed_tool_type = nullptr;
+  std::vector<OutputFile> output;
+  bool result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+                                               &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  ASSERT_EQ(2u, output.size());
+  EXPECT_EQ("input.txt.one", output[0].value());
+  EXPECT_EQ("input.txt.two", output[1].value());
+
+  // Test GetOutputsAsSourceFiles(). It should give both outputs for each of the
+  // two inputs.
+  std::vector<SourceFile> computed_outputs;
+  EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+                                             &computed_outputs, &err));
+  ASSERT_EQ(4u, computed_outputs.size());
+  EXPECT_EQ("//out/Debug/source_file1.txt.one", computed_outputs[0].value());
+  EXPECT_EQ("//out/Debug/source_file1.txt.two", computed_outputs[1].value());
+  EXPECT_EQ("//out/Debug/source_file2.txt.one", computed_outputs[2].value());
+  EXPECT_EQ("//out/Debug/source_file2.txt.two", computed_outputs[3].value());
+}
+
+// Tests Target::GetOutputFilesForSource for action targets (these just list the
+// output of the action as the result of all possible inputs). This should also
+// cover everything other than binary, action_foreach, and copy targets.
+TEST_F(TargetTest, GetOutputFilesForSource_Action) {
+  TestWithScope setup;
+
+  TestTarget target(setup, "//a:a", Target::ACTION);
+  target.sources().push_back(SourceFile("//a/source_file1.txt"));
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/one", "//out/Debug/two");
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const char* computed_tool_type = nullptr;
+  std::vector<OutputFile> output;
+  bool result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+                                               &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  ASSERT_EQ(2u, output.size());
+  EXPECT_EQ("one", output[0].value());
+  EXPECT_EQ("two", output[1].value());
+
+  // Test GetOutputsAsSourceFiles(). It should give the listed outputs.
+  std::vector<SourceFile> computed_outputs;
+  EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+                                             &computed_outputs, &err));
+  ASSERT_EQ(2u, computed_outputs.size());
+  EXPECT_EQ("//out/Debug/one", computed_outputs[0].value());
+  EXPECT_EQ("//out/Debug/two", computed_outputs[1].value());
+
+  // Test that the copy target type behaves the same. This target requires only
+  // one output.
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/one");
+  target.set_output_type(Target::COPY_FILES);
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  result = target.GetOutputFilesForSource(SourceFile("//source/input.txt"),
+                                          &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+  ASSERT_EQ(1u, output.size());
+  EXPECT_EQ("one", output[0].value());
+
+  // Test GetOutputsAsSourceFiles() for the copy case.
+  EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
+                                             &computed_outputs, &err));
+  ASSERT_EQ(1u, computed_outputs.size()) << computed_outputs.size();
+  EXPECT_EQ("//out/Debug/one", computed_outputs[0].value());
+}
+
+// Shared libraries should be inherited across public shared library
+// boundaries.
+TEST_F(TargetTest, SharedInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  // Create two leaf shared libraries.
+  TestTarget pub(setup, "//foo:pub", Target::SHARED_LIBRARY);
+  ASSERT_TRUE(pub.OnResolved(&err));
+
+  TestTarget priv(setup, "//foo:priv", Target::SHARED_LIBRARY);
+  ASSERT_TRUE(priv.OnResolved(&err));
+
+  // Intermediate shared library with the leaf shared libraries as
+  // dependencies, one public, one private.
+  TestTarget inter(setup, "//foo:inter", Target::SHARED_LIBRARY);
+  inter.public_deps().push_back(LabelTargetPair(&pub));
+  inter.private_deps().push_back(LabelTargetPair(&priv));
+  ASSERT_TRUE(inter.OnResolved(&err));
+
+  // The intermediate shared library should have both "pub" and "priv" in its
+  // inherited libraries.
+  std::vector<const Target*> inter_inherited =
+      inter.inherited_libraries().GetOrdered();
+  ASSERT_EQ(2u, inter_inherited.size());
+  EXPECT_EQ(&pub, inter_inherited[0]);
+  EXPECT_EQ(&priv, inter_inherited[1]);
+
+  // Make a toplevel executable target depending on the intermediate one.
+  TestTarget exe(setup, "//foo:exe", Target::SHARED_LIBRARY);
+  exe.private_deps().push_back(LabelTargetPair(&inter));
+  ASSERT_TRUE(exe.OnResolved(&err));
+
+  // The exe's inherited libraries should be "inter" (because it depended
+  // directly on it) and "pub" (because inter depended publicly on it).
+  std::vector<const Target*> exe_inherited =
+      exe.inherited_libraries().GetOrdered();
+  ASSERT_EQ(2u, exe_inherited.size());
+  EXPECT_EQ(&inter, exe_inherited[0]);
+  EXPECT_EQ(&pub, exe_inherited[1]);
+}
+
+TEST_F(TargetTest, GeneratedInputs) {
+  TestWithScope setup;
+  Err err;
+
+  SourceFile generated_file("//out/Debug/generated.cc");
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget non_existent_generator(setup, "//foo:non_existent_generator",
+                                    Target::EXECUTABLE);
+  non_existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(non_existent_generator.OnResolved(&err)) << err.message();
+  AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
+                                           generated_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // Make a target that generates the file.
+  TestTarget generator(setup, "//foo:generator", Target::ACTION);
+  generator.action_values().outputs() =
+      SubstitutionList::MakeForTest(generated_file.value().c_str());
+  err = Err();
+  EXPECT_TRUE(generator.OnResolved(&err)) << err.message();
+
+  // A target that depends on the generator that uses the file as a source
+  // should be OK. This uses a private dep (will be used later).
+  TestTarget existent_generator(setup, "//foo:existent_generator",
+                                Target::SHARED_LIBRARY);
+  existent_generator.sources().push_back(generated_file);
+  existent_generator.private_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(existent_generator.OnResolved(&err)) << err.message();
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+
+  // A target that depends on the previous one should *not* be allowed to
+  // use the generated file, because existent_generator used private deps.
+  // This is:
+  //    indirect_private --> existent_generator --[private]--> generator
+  TestTarget indirect_private(setup, "//foo:indirect_private",
+                              Target::EXECUTABLE);
+  indirect_private.sources().push_back(generated_file);
+  indirect_private.public_deps().push_back(
+      LabelTargetPair(&existent_generator));
+  EXPECT_TRUE(indirect_private.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&indirect_private, generated_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // Now make a chain like the above but with all public deps, it should be OK.
+  TestTarget existent_public(setup, "//foo:existent_public",
+                             Target::SHARED_LIBRARY);
+  existent_public.public_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(existent_public.OnResolved(&err)) << err.message();
+  TestTarget indirect_public(setup, "//foo:indirect_public",
+                             Target::EXECUTABLE);
+  indirect_public.sources().push_back(generated_file);
+  indirect_public.public_deps().push_back(LabelTargetPair(&existent_public));
+  EXPECT_TRUE(indirect_public.OnResolved(&err)) << err.message();
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+}
+
+// This is sort of a Scheduler test, but is related to the above test more.
+TEST_F(TargetTest, WriteFileGeneratedInputs) {
+  TestWithScope setup;
+  Err err;
+
+  SourceFile generated_file("//out/Debug/generated.data");
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget non_existent_generator(setup, "//foo:non_existent_generator",
+                                    Target::EXECUTABLE);
+  non_existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(non_existent_generator.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&non_existent_generator,
+                                           generated_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've decared we write it.
+  TestTarget existent_generator(setup, "//foo:existent_generator",
+                                Target::EXECUTABLE);
+  existent_generator.sources().push_back(generated_file);
+  EXPECT_TRUE(existent_generator.OnResolved(&err));
+  scheduler().AddWrittenFile(generated_file);
+
+  // Should be OK.
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+}
+
+TEST_F(TargetTest, WriteRuntimeDepsGeneratedInputs) {
+  TestWithScope setup;
+  Err err;
+
+  SourceFile source_file("//out/Debug/generated.runtime_deps");
+  OutputFile output_file(setup.build_settings(), source_file);
+
+  TestTarget generator(setup, "//foo:generator", Target::EXECUTABLE);
+  generator.set_write_runtime_deps_output(output_file);
+  g_scheduler->AddWriteRuntimeDepsTarget(&generator);
+
+  TestTarget middle_data_dep(setup, "//foo:middle", Target::EXECUTABLE);
+  middle_data_dep.data_deps().push_back(LabelTargetPair(&generator));
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget dep_missing(setup, "//foo:no_dep", Target::EXECUTABLE);
+  dep_missing.sources().push_back(source_file);
+  EXPECT_TRUE(dep_missing.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_missing, source_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it.
+  TestTarget dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+  dep_present.sources().push_back(source_file);
+  dep_present.private_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+
+  // This target has a generated file and we've indirectly dependended on it
+  // via data_deps.
+  TestTarget dep_indirect(setup, "//foo:with_dep", Target::EXECUTABLE);
+  dep_indirect.sources().push_back(source_file);
+  dep_indirect.data_deps().push_back(LabelTargetPair(&middle_data_dep));
+  EXPECT_TRUE(dep_indirect.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_indirect, source_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it
+  // via data_deps.
+  TestTarget data_dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+  data_dep_present.sources().push_back(source_file);
+  data_dep_present.data_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(data_dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+}
+
+// Tests that intermediate object files generated by binary targets are also
+// considered generated for the purposes of input checking. Above, we tested
+// the failure cases for generated inputs, so here only test .o files that are
+// present.
+TEST_F(TargetTest, ObjectGeneratedInputs) {
+  TestWithScope setup;
+  Err err;
+
+  // This target compiles the source.
+  SourceFile source_file("//source.cc");
+  TestTarget source_generator(setup, "//:source_target", Target::SOURCE_SET);
+  source_generator.sources().push_back(source_file);
+  EXPECT_TRUE(source_generator.OnResolved(&err));
+
+  // This is the object file that the test toolchain generates for the source.
+  SourceFile object_file("//out/Debug/obj/source_target.source.o");
+
+  TestTarget final_target(setup, "//:final", Target::ACTION);
+  final_target.config_values().inputs().push_back(object_file);
+  EXPECT_TRUE(final_target.OnResolved(&err));
+
+  AssertSchedulerHasOneUnknownFileMatching(&final_target, object_file);
+}
+
+TEST_F(TargetTest, ResolvePrecompiledHeaders) {
+  TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+
+  // Target with no settings, no configs, should be a no-op.
+  EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err));
+
+  // Config with PCH values.
+  Config config_1(setup.settings(), Label(SourceDir("//foo/"), "c1"));
+  std::string pch_1("pch.h");
+  SourceFile pcs_1("//pcs.cc");
+  config_1.own_values().set_precompiled_header(pch_1);
+  config_1.own_values().set_precompiled_source(pcs_1);
+  ASSERT_TRUE(config_1.OnResolved(&err));
+  target.configs().push_back(LabelConfigPair(&config_1));
+
+  // No PCH info specified on TargetTest, but the config specifies one, the
+  // values should get copied to the target.
+  EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err));
+  EXPECT_EQ(pch_1, target.config_values().precompiled_header());
+  EXPECT_TRUE(target.config_values().precompiled_source() == pcs_1);
+
+  // Now both target and config have matching PCH values. Resolving again
+  // should be a no-op since they all match.
+  EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err));
+  EXPECT_TRUE(target.config_values().precompiled_header() == pch_1);
+  EXPECT_TRUE(target.config_values().precompiled_source() == pcs_1);
+
+  // Second config with different PCH values.
+  Config config_2(setup.settings(), Label(SourceDir("//foo/"), "c2"));
+  std::string pch_2("pch2.h");
+  SourceFile pcs_2("//pcs2.cc");
+  config_2.own_values().set_precompiled_header(pch_2);
+  config_2.own_values().set_precompiled_source(pcs_2);
+  ASSERT_TRUE(config_2.OnResolved(&err));
+  target.configs().push_back(LabelConfigPair(&config_2));
+
+  // This should be an error since they don't match.
+  EXPECT_FALSE(target.ResolvePrecompiledHeaders(&err));
+
+  // Make sure the proper labels are blamed.
+  EXPECT_EQ(
+      "The target //foo:bar\n"
+      "has conflicting precompiled header settings.\n"
+      "\n"
+      "From //foo:bar\n"
+      "  header: pch.h\n"
+      "  source: //pcs.cc\n"
+      "\n"
+      "From //foo:c2\n"
+      "  header: pch2.h\n"
+      "  source: //pcs2.cc",
+      err.help_text());
+}
+
+TEST_F(TargetTest, AssertNoDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // A target.
+  TestTarget a(setup, "//a", Target::SHARED_LIBRARY);
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B depends on A and has an assert_no_deps for a random dir.
+  TestTarget b(setup, "//b", Target::SHARED_LIBRARY);
+  b.private_deps().push_back(LabelTargetPair(&a));
+  b.assert_no_deps().push_back(LabelPattern(LabelPattern::RECURSIVE_DIRECTORY,
+                                            SourceDir("//disallowed/"),
+                                            std::string(), Label()));
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  LabelPattern disallow_a(LabelPattern::RECURSIVE_DIRECTORY, SourceDir("//a/"),
+                          std::string(), Label());
+
+  // C depends on B and disallows depending on A. This should fail.
+  TestTarget c(setup, "//c", Target::EXECUTABLE);
+  c.private_deps().push_back(LabelTargetPair(&b));
+  c.assert_no_deps().push_back(disallow_a);
+  ASSERT_FALSE(c.OnResolved(&err));
+
+  // Validate the error message has the proper path.
+  EXPECT_EQ(
+      "//c:c has an assert_no_deps entry:\n"
+      "  //a/*\n"
+      "which fails for the dependency path:\n"
+      "  //c:c ->\n"
+      "  //b:b ->\n"
+      "  //a:a",
+      err.help_text());
+  err = Err();
+
+  // Add an intermediate executable with: exe -> b -> a
+  TestTarget exe(setup, "//exe", Target::EXECUTABLE);
+  exe.private_deps().push_back(LabelTargetPair(&b));
+  ASSERT_TRUE(exe.OnResolved(&err));
+
+  // D depends on the executable and disallows depending on A. Since there is
+  // an intermediate executable, this should be OK.
+  TestTarget d(setup, "//d", Target::EXECUTABLE);
+  d.private_deps().push_back(LabelTargetPair(&exe));
+  d.assert_no_deps().push_back(disallow_a);
+  ASSERT_TRUE(d.OnResolved(&err));
+
+  // A2 disallows depending on anything in its own directory, but the
+  // assertions should not match the target itself so this should be OK.
+  TestTarget a2(setup, "//a:a2", Target::EXECUTABLE);
+  a2.assert_no_deps().push_back(disallow_a);
+  ASSERT_TRUE(a2.OnResolved(&err));
+}
+
+TEST_F(TargetTest, PullRecursiveBundleData) {
+  TestWithScope setup;
+  Err err;
+
+  // We have the following dependency graph:
+  // A (create_bundle) -> B (bundle_data)
+  //                  \-> C (create_bundle) -> D (bundle_data)
+  //                  \-> E (group) -> F (bundle_data)
+  //                               \-> B (bundle_data)
+  TestTarget a(setup, "//foo:a", Target::CREATE_BUNDLE);
+  TestTarget b(setup, "//foo:b", Target::BUNDLE_DATA);
+  TestTarget c(setup, "//foo:c", Target::CREATE_BUNDLE);
+  TestTarget d(setup, "//foo:d", Target::BUNDLE_DATA);
+  TestTarget e(setup, "//foo:e", Target::GROUP);
+  TestTarget f(setup, "//foo:f", Target::BUNDLE_DATA);
+  a.public_deps().push_back(LabelTargetPair(&b));
+  a.public_deps().push_back(LabelTargetPair(&c));
+  a.public_deps().push_back(LabelTargetPair(&e));
+  c.public_deps().push_back(LabelTargetPair(&d));
+  e.public_deps().push_back(LabelTargetPair(&f));
+  e.public_deps().push_back(LabelTargetPair(&b));
+
+  a.bundle_data().root_dir() = SourceDir("//out/foo_a.bundle");
+  a.bundle_data().resources_dir() = SourceDir("//out/foo_a.bundle/Resources");
+
+  b.sources().push_back(SourceFile("//foo/b1.txt"));
+  b.sources().push_back(SourceFile("//foo/b2.txt"));
+  b.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  ASSERT_TRUE(b.OnResolved(&err));
+
+  c.bundle_data().root_dir() = SourceDir("//out/foo_c.bundle");
+  c.bundle_data().resources_dir() = SourceDir("//out/foo_c.bundle/Resources");
+
+  d.sources().push_back(SourceFile("//foo/d.txt"));
+  d.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  ASSERT_TRUE(d.OnResolved(&err));
+
+  f.sources().push_back(SourceFile("//foo/f1.txt"));
+  f.sources().push_back(SourceFile("//foo/f2.txt"));
+  f.sources().push_back(SourceFile("//foo/f3.txt"));
+  f.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  f.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooEmpty-29.png"));
+  f.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooEmpty-29@2x.png"));
+  f.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooEmpty-29@3x.png"));
+  f.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  ASSERT_TRUE(f.OnResolved(&err));
+
+  ASSERT_TRUE(e.OnResolved(&err));
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // A gets its data from B and F.
+  ASSERT_EQ(a.bundle_data().file_rules().size(), 2u);
+  ASSERT_EQ(a.bundle_data().file_rules()[0].sources().size(), 2u);
+  ASSERT_EQ(a.bundle_data().file_rules()[1].sources().size(), 3u);
+  ASSERT_EQ(a.bundle_data().assets_catalog_sources().size(), 1u);
+  ASSERT_EQ(a.bundle_data().bundle_deps().size(), 2u);
+
+  // C gets its data from D.
+  ASSERT_EQ(c.bundle_data().file_rules().size(), 1u);
+  ASSERT_EQ(c.bundle_data().file_rules()[0].sources().size(), 1u);
+  ASSERT_EQ(c.bundle_data().bundle_deps().size(), 1u);
+
+  // E does not have any bundle_data information but gets a list of
+  // bundle_deps to propagate them during target resolution.
+  ASSERT_TRUE(e.bundle_data().file_rules().empty());
+  ASSERT_TRUE(e.bundle_data().assets_catalog_sources().empty());
+  ASSERT_EQ(e.bundle_data().bundle_deps().size(), 2u);
+}
+
+TEST(TargetTest, CollectMetadataNoRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("b", b_expected));
+
+  one.metadata().set_source_dir(SourceDir("/usr/home/files/"));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+                  &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  EXPECT_EQ(result, expected);
+}
+
+TEST(TargetTest, CollectMetadataWithRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("b", b_expected));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_2_expected));
+
+  one.public_deps().push_back(LabelTargetPair(&two));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+                  &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "bar"));
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  EXPECT_EQ(result, expected);
+}
+
+TEST(TargetTest, CollectMetadataWithBarrier) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(Value(nullptr, "two"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("walk", walk_expected));
+
+  TestTarget two(setup, "//foo/two:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_2_expected));
+
+  TestTarget three(setup, "//foo:three", Target::SOURCE_SET);
+  Value a_3_expected(nullptr, Value::LIST);
+  a_3_expected.list_value().push_back(Value(nullptr, "baz"));
+  three.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_3_expected));
+
+  one.private_deps().push_back(LabelTargetPair(&two));
+  one.public_deps().push_back(LabelTargetPair(&three));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+                  &err);
+  EXPECT_FALSE(err.has_error()) << err.message();
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "bar"));
+  expected.push_back(Value(nullptr, "foo"));
+  EXPECT_EQ(result, expected) << result.size();
+}
+
+TEST(TargetTest, CollectMetadataWithError) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(Value(nullptr, "//foo:missing"));
+  one.metadata().contents().insert(
+      std::pair<std::string_view, Value>("walk", walk_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, SourceDir(), false, &result, &targets,
+                  &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(err.message(),
+            "I was expecting //foo:missing(//toolchain:default) to be a "
+            "dependency of //foo:one(//toolchain:default). "
+            "Make sure it's included in the deps or data_deps, and that you've "
+            "specified the appropriate toolchain.")
+      << err.message();
+}
+
+TEST_F(TargetTest, WriteMetadataCollection) {
+  TestWithScope setup;
+  Err err;
+
+  SourceFile source_file("//out/Debug/metadata.json");
+  OutputFile output_file(setup.build_settings(), source_file);
+
+  TestTarget generator(setup, "//foo:write", Target::GENERATED_FILE);
+  generator.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/metadata.json");
+  EXPECT_TRUE(generator.OnResolved(&err));
+
+  TestTarget middle_data_dep(setup, "//foo:middle", Target::EXECUTABLE);
+  middle_data_dep.data_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(middle_data_dep.OnResolved(&err));
+
+  // This target has a generated metadata input and no dependency makes it.
+  TestTarget dep_missing(setup, "//foo:no_dep", Target::EXECUTABLE);
+  dep_missing.sources().push_back(source_file);
+  EXPECT_TRUE(dep_missing.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_missing, source_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it.
+  TestTarget dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+  dep_present.sources().push_back(source_file);
+  dep_present.private_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+
+  // This target has a generated file and we've indirectly dependended on it
+  // via data_deps.
+  TestTarget dep_indirect(setup, "//foo:indirect_dep", Target::EXECUTABLE);
+  dep_indirect.sources().push_back(source_file);
+  dep_indirect.data_deps().push_back(LabelTargetPair(&middle_data_dep));
+  EXPECT_TRUE(dep_indirect.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_indirect, source_file);
+  scheduler().ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it
+  // via data_deps.
+  TestTarget data_dep_present(setup, "//foo:with_data_dep", Target::EXECUTABLE);
+  data_dep_present.sources().push_back(source_file);
+  data_dep_present.data_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(data_dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler().GetUnknownGeneratedInputs().empty());
+}
+
+// Tests that modulemap files use the cxx_module tool.
+TEST_F(TargetTest, ModuleMap) {
+  TestWithScope setup;
+
+  Toolchain toolchain(setup.settings(), Label(SourceDir("//tc/"), "tc"));
+
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolCxxModule);
+  CTool* cxx_module = tool->AsC();
+  cxx_module->set_outputs(
+      SubstitutionList::MakeForTest("{{source_file_part}}.pcm"));
+  toolchain.SetTool(std::move(tool));
+
+  Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.SetToolchain(&toolchain);
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  const char* computed_tool_type = nullptr;
+  std::vector<OutputFile> output;
+  bool result = target.GetOutputFilesForSource(
+      SourceFile("//source/input.modulemap"), &computed_tool_type, &output);
+  ASSERT_TRUE(result);
+  EXPECT_EQ(std::string("cxx_module"), std::string(computed_tool_type));
+
+  // Outputs are relative to the build directory "//out/Debug/".
+  ASSERT_EQ(1u, output.size());
+  EXPECT_EQ("input.modulemap.pcm", output[0].value()) << output[0].value();
+}
diff --git a/src/gn/template.cc b/src/gn/template.cc
new file mode 100644 (file)
index 0000000..f0eca28
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/template.h"
+
+#include <memory>
+#include <utility>
+
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/scope_per_file_provider.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+
+Template::Template(const Scope* scope, const FunctionCallNode* def)
+    : closure_(scope->MakeClosure()), definition_(def) {}
+
+Template::Template(std::unique_ptr<Scope> scope, const FunctionCallNode* def)
+    : closure_(std::move(scope)), definition_(def) {}
+
+Template::~Template() = default;
+
+Value Template::Invoke(Scope* scope,
+                       const FunctionCallNode* invocation,
+                       const std::string& template_name,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) const {
+  // Don't allow templates to be executed from imported files. Imports are for
+  // simple values only.
+  if (!EnsureNotProcessingImport(invocation, scope, err))
+    return Value();
+
+  // First run the invocation's block. Need to allocate the scope on the heap
+  // so we can pass ownership to the template.
+  std::unique_ptr<Scope> invocation_scope = std::make_unique<Scope>(scope);
+  if (!FillTargetBlockScope(scope, invocation, template_name, block, args,
+                            invocation_scope.get(), err))
+    return Value();
+
+  {
+    // Don't allow the block of the template invocation to include other
+    // targets configs, or template invocations. This must only be applied
+    // to the invoker's block rather than the whole function because the
+    // template execution itself must be able to define targets, etc.
+    NonNestableBlock non_nestable(scope, invocation, "template invocation");
+    if (!non_nestable.Enter(err))
+      return Value();
+
+    block->Execute(invocation_scope.get(), err);
+    if (err->has_error())
+      return Value();
+  }
+
+  // Set up the scope to run the template and set the current directory for the
+  // template (which ScopePerFileProvider uses to base the target-related
+  // variables target_gen_dir and target_out_dir on) to be that of the invoker.
+  // This way, files don't have to be rebased and target_*_dir works the way
+  // people expect (otherwise its to easy to be putting generated files in the
+  // gen dir corresponding to an imported file).
+  Scope template_scope(closure_.get());
+  template_scope.set_source_dir(scope->GetSourceDir());
+
+  // Propagate build dependency files from invoker scope (template scope already
+  // propagated via parent scope).
+  template_scope.AddBuildDependencyFiles(
+      invocation_scope->build_dependency_files());
+
+  ScopePerFileProvider per_file_provider(&template_scope, true);
+
+  // Targets defined in the template go in the collector for the invoking file.
+  template_scope.set_item_collector(scope->GetItemCollector());
+
+  // We jump through some hoops to avoid copying the invocation scope when
+  // setting it in the template scope (since the invocation scope may have
+  // large lists of source files in it and could be expensive to copy).
+  //
+  // Scope.SetValue will copy the value which will in turn copy the scope, but
+  // if we instead create a value and then set the scope on it, the copy can
+  // be avoided.
+  template_scope.SetValue(variables::kInvoker,
+                          Value(nullptr, std::unique_ptr<Scope>()), invocation);
+  Value* invoker_value = template_scope.GetMutableValue(
+      variables::kInvoker, Scope::SEARCH_NESTED, false);
+  invoker_value->SetScopeValue(std::move(invocation_scope));
+  template_scope.set_source_dir(scope->GetSourceDir());
+
+  const std::string_view target_name(variables::kTargetName);
+  template_scope.SetValue(
+      target_name, Value(invocation, args[0].string_value()), invocation);
+
+  // Actually run the template code.
+  Value result = definition_->block()->Execute(&template_scope, err);
+  if (err->has_error()) {
+    // If there was an error, append the caller location so the error message
+    // displays a stack trace of how it got here.
+    err->AppendSubErr(Err(invocation, "whence it was called."));
+    return Value();
+  }
+
+  // Check for unused variables in the invocation scope. This will find typos
+  // of things the caller meant to pass to the template but the template didn't
+  // read out.
+  //
+  // This is a bit tricky because it's theoretically possible for the template
+  // to overwrite the value of "invoker" and free the Scope owned by the
+  // value. So we need to look it up again and don't do anything if it doesn't
+  // exist.
+  invoker_value = template_scope.GetMutableValue(variables::kInvoker,
+                                                 Scope::SEARCH_NESTED, false);
+  if (invoker_value && invoker_value->type() == Value::SCOPE) {
+    if (!invoker_value->scope_value()->CheckForUnusedVars(err))
+      return Value();
+  }
+
+  // Check for unused variables in the template itself.
+  if (!template_scope.CheckForUnusedVars(err))
+    return Value();
+
+  return result;
+}
+
+LocationRange Template::GetDefinitionRange() const {
+  return definition_->GetRange();
+}
diff --git a/src/gn/template.h b/src/gn/template.h
new file mode 100644 (file)
index 0000000..6386ba9
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TEMPLATE_H_
+#define TOOLS_GN_TEMPLATE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+
+class BlockNode;
+class Err;
+class FunctionCallNode;
+class LocationRange;
+class Scope;
+class Value;
+
+// Represents the information associated with a template() call in GN, which
+// includes a closure and the code to run when the template is invoked.
+//
+// This class is immutable so we can reference it from multiple threads without
+// locking. Normally, this will be associated with a .gni file and then a
+// reference will be taken by each .gn file that imports it. These files might
+// execute the template in parallel.
+class Template : public base::RefCountedThreadSafe<Template> {
+ public:
+  // Makes a new closure based on the given scope.
+  Template(const Scope* scope, const FunctionCallNode* def);
+
+  // Takes ownership of a previously-constructed closure.
+  Template(std::unique_ptr<Scope> closure, const FunctionCallNode* def);
+
+  // Invoke the template. The values correspond to the state of the code
+  // invoking the template. The template name needs to be supplied since the
+  // template object itself doesn't know what name the calling code is using
+  // to refer to it (this is used to set defaults).
+  Value Invoke(Scope* scope,
+               const FunctionCallNode* invocation,
+               const std::string& template_name,
+               const std::vector<Value>& args,
+               BlockNode* block,
+               Err* err) const;
+
+  // Returns the location range where this template was defined.
+  LocationRange GetDefinitionRange() const;
+
+ private:
+  friend class base::RefCountedThreadSafe<Template>;
+
+  Template();
+  ~Template();
+
+  // It's important that this Scope is const. A template can be referenced by
+  // the root BUILDCONFIG file and then duplicated to all threads. Therefore,
+  // this scope must be usable from multiple threads at the same time.
+  //
+  // When executing a template, a new scope will be created as a child of this
+  // one, which will reference it as mutable or not according to the mutability
+  // of this value.
+  std::unique_ptr<const Scope> closure_;
+
+  const FunctionCallNode* definition_;
+};
+
+#endif  // TOOLS_GN_TEMPLATE_H_
diff --git a/src/gn/template_unittest.cc b/src/gn/template_unittest.cc
new file mode 100644 (file)
index 0000000..a8c88dc
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_number_conversions.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(Template, Basic) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  print(invoker.bar)\n"
+      "}\n"
+      "foo(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ("lala\n42\n", setup.print_output());
+}
+
+TEST(Template, UnusedTargetNameShouldThrowError) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(invoker.bar)\n"
+      "}\n"
+      "foo(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(Template, UnusedInvokerShouldThrowError) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "}\n"
+      "foo(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(Template, UnusedVarInInvokerShouldThrowError) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  print(invoker.bar)\n"
+      "}\n"
+      "foo(\"lala\") {\n"
+      "  bar = 42\n"
+      "  baz = [ \"foo\" ]\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+// Previous versions of the template implementation would copy templates by
+// value when it makes a closure. Doing a sequence of them means that every new
+// one copies all previous ones, which gives a significant blow-up in memory.
+// If this test doesn't crash with out-of-memory, it passed.
+TEST(Template, MemoryBlowUp) {
+  TestWithScope setup;
+  std::string code;
+  for (int i = 0; i < 100; i++)
+    code += "template(\"test" + base::IntToString(i) + "\") {}\n";
+
+  TestParseInput input(code);
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(input.has_error());
+}
diff --git a/src/gn/test_with_scheduler.cc b/src/gn/test_with_scheduler.cc
new file mode 100644 (file)
index 0000000..8022c5a
--- /dev/null
@@ -0,0 +1,8 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/test_with_scheduler.h"
+
+TestWithScheduler::TestWithScheduler() = default;
+TestWithScheduler::~TestWithScheduler() = default;
diff --git a/src/gn/test_with_scheduler.h b/src/gn/test_with_scheduler.h
new file mode 100644 (file)
index 0000000..4ae3697
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TEST_WITH_SCHEDULER_H_
+#define TOOLS_GN_TEST_WITH_SCHEDULER_H_
+
+#include "base/macros.h"
+#include "gn/scheduler.h"
+#include "util/msg_loop.h"
+#include "util/test/test.h"
+
+class TestWithScheduler : public testing::Test {
+ protected:
+  TestWithScheduler();
+  ~TestWithScheduler() override;
+
+  Scheduler& scheduler() { return scheduler_; }
+
+ private:
+  MsgLoop run_loop_;
+  Scheduler scheduler_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestWithScheduler);
+};
+
+#endif  // TOOLS_GN_TEST_WITH_SCHEDULER_H_
diff --git a/src/gn/test_with_scope.cc b/src/gn/test_with_scope.cc
new file mode 100644 (file)
index 0000000..583eebb
--- /dev/null
@@ -0,0 +1,349 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/test_with_scope.h"
+
+#include <memory>
+#include <utility>
+
+#include "gn/parser.h"
+#include "gn/tokenizer.h"
+
+namespace {
+
+BuildSettings CreateBuildSettingsForTest() {
+  BuildSettings build_settings;
+  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+  return build_settings;
+}
+
+}  // namespace
+
+TestWithScope::TestWithScope()
+    : build_settings_(CreateBuildSettingsForTest()),
+      settings_(&build_settings_, std::string()),
+      toolchain_(&settings_, Label(SourceDir("//toolchain/"), "default")),
+      scope_(&settings_),
+      scope_progammatic_provider_(&scope_, true) {
+  build_settings_.set_print_callback(
+      [this](const std::string& str) { AppendPrintOutput(str); });
+
+  settings_.set_toolchain_label(toolchain_.label());
+  settings_.set_default_toolchain_label(toolchain_.label());
+
+  SetupToolchain(&toolchain_);
+  scope_.set_item_collector(&items_);
+}
+
+TestWithScope::~TestWithScope() = default;
+
+Label TestWithScope::ParseLabel(const std::string& str) const {
+  Err err;
+  Label result = Label::Resolve(SourceDir("//"), std::string_view(),
+                                toolchain_.label(), Value(nullptr, str), &err);
+  CHECK(!err.has_error());
+  return result;
+}
+
+bool TestWithScope::ExecuteSnippet(const std::string& str, Err* err) {
+  TestParseInput input(str);
+  if (input.has_error()) {
+    *err = input.parse_err();
+    return false;
+  }
+
+  size_t first_item = items_.size();
+  input.parsed()->Execute(&scope_, err);
+  if (err->has_error())
+    return false;
+
+  for (size_t i = first_item; i < items_.size(); ++i) {
+    CHECK(items_[i]->AsTarget() != nullptr)
+        << "Only targets are supported in ExecuteSnippet()";
+    items_[i]->AsTarget()->SetToolchain(&toolchain_);
+    if (!items_[i]->OnResolved(err))
+      return false;
+  }
+  return true;
+}
+
+Value TestWithScope::ExecuteExpression(const std::string& expr, Err* err) {
+  InputFile input_file(SourceFile("//test"));
+  input_file.SetContents(expr);
+
+  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err);
+  if (err->has_error()) {
+    return Value();
+  }
+  std::unique_ptr<ParseNode> node = Parser::ParseExpression(tokens, err);
+  if (err->has_error()) {
+    return Value();
+  }
+
+  return node->Execute(&scope_, err);
+}
+
+// static
+void TestWithScope::SetupToolchain(Toolchain* toolchain, bool use_toc) {
+  Err err;
+
+  // CC
+  std::unique_ptr<Tool> cc_tool = Tool::CreateTool(CTool::kCToolCc);
+  SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool.get());
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  toolchain->SetTool(std::move(cc_tool));
+
+  // CXX
+  std::unique_ptr<Tool> cxx_tool = Tool::CreateTool(CTool::kCToolCxx);
+  SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool.get());
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_command_launcher("launcher");
+  toolchain->SetTool(std::move(cxx_tool));
+
+  // OBJC
+  std::unique_ptr<Tool> objc_tool = Tool::CreateTool(CTool::kCToolObjC);
+  SetCommandForTool(
+      "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} "
+      "{{include_dirs}} -o {{output}}",
+      objc_tool.get());
+  objc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  toolchain->SetTool(std::move(objc_tool));
+
+  // OBJC
+  std::unique_ptr<Tool> objcxx_tool = Tool::CreateTool(CTool::kCToolObjCxx);
+  SetCommandForTool(
+      "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} "
+      "{{include_dirs}} -o {{output}}",
+      objcxx_tool.get());
+  objcxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  toolchain->SetTool(std::move(objcxx_tool));
+
+  // Don't use RC and ASM tools in unit tests yet. Add here if needed.
+
+  // ALINK
+  std::unique_ptr<Tool> alink = Tool::CreateTool(CTool::kCToolAlink);
+  CTool* alink_tool = alink->AsC();
+  SetCommandForTool("ar {{output}} {{source}}", alink_tool);
+  alink_tool->set_lib_switch("-l");
+  alink_tool->set_lib_dir_switch("-L");
+  alink_tool->set_output_prefix("lib");
+  alink_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}.a"));
+  toolchain->SetTool(std::move(alink));
+
+  // SOLINK
+  std::unique_ptr<Tool> solink = Tool::CreateTool(CTool::kCToolSolink);
+  CTool* solink_tool = solink->AsC();
+  SetCommandForTool(
+      "ld -shared -o {{target_output_name}}.so {{inputs}} "
+      "{{ldflags}} {{libs}}",
+      solink_tool);
+  solink_tool->set_lib_switch("-l");
+  solink_tool->set_lib_dir_switch("-L");
+  solink_tool->set_output_prefix("lib");
+  solink_tool->set_default_output_extension(".so");
+  if (use_toc) {
+    solink_tool->set_outputs(SubstitutionList::MakeForTest(
+        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.TOC",
+        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"));
+    solink_tool->set_link_output(SubstitutionPattern::MakeForTest(
+        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"));
+    solink_tool->set_depend_output(SubstitutionPattern::MakeForTest(
+        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.TOC"));
+  } else {
+    solink_tool->set_outputs(SubstitutionList::MakeForTest(
+        "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  }
+  toolchain->SetTool(std::move(solink));
+
+  // SOLINK_MODULE
+  std::unique_ptr<Tool> solink_module =
+      Tool::CreateTool(CTool::kCToolSolinkModule);
+  CTool* solink_module_tool = solink_module->AsC();
+  SetCommandForTool(
+      "ld -bundle -o {{target_output_name}}.so {{inputs}} "
+      "{{ldflags}} {{libs}}",
+      solink_module_tool);
+  solink_module_tool->set_lib_switch("-l");
+  solink_module_tool->set_lib_dir_switch("-L");
+  solink_module_tool->set_output_prefix("lib");
+  solink_module_tool->set_default_output_extension(".so");
+  solink_module_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(solink_module));
+
+  // LINK
+  std::unique_ptr<Tool> link = Tool::CreateTool(CTool::kCToolLink);
+  CTool* link_tool = link->AsC();
+  SetCommandForTool(
+      "ld -o {{target_output_name}} {{source}} "
+      "{{ldflags}} {{libs}}",
+      link_tool);
+  link_tool->set_lib_switch("-l");
+  link_tool->set_lib_dir_switch("-L");
+  link_tool->set_outputs(
+      SubstitutionList::MakeForTest("{{root_out_dir}}/{{target_output_name}}"));
+  toolchain->SetTool(std::move(link));
+
+  // STAMP
+  std::unique_ptr<Tool> stamp_tool =
+      Tool::CreateTool(GeneralTool::kGeneralToolStamp);
+  SetCommandForTool("touch {{output}}", stamp_tool.get());
+  toolchain->SetTool(std::move(stamp_tool));
+
+  // COPY
+  std::unique_ptr<Tool> copy_tool =
+      Tool::CreateTool(GeneralTool::kGeneralToolCopy);
+  SetCommandForTool("cp {{source}} {{output}}", copy_tool.get());
+  toolchain->SetTool(std::move(copy_tool));
+
+  // COPY_BUNDLE_DATA
+  std::unique_ptr<Tool> copy_bundle_data_tool =
+      Tool::CreateTool(GeneralTool::kGeneralToolCopyBundleData);
+  SetCommandForTool("cp {{source}} {{output}}", copy_bundle_data_tool.get());
+  toolchain->SetTool(std::move(copy_bundle_data_tool));
+
+  // COMPILE_XCASSETS
+  std::unique_ptr<Tool> compile_xcassets_tool =
+      Tool::CreateTool(GeneralTool::kGeneralToolCompileXCAssets);
+  SetCommandForTool("touch {{output}}", compile_xcassets_tool.get());
+  toolchain->SetTool(std::move(compile_xcassets_tool));
+
+  // RUST
+  std::unique_ptr<Tool> rustc_tool = Tool::CreateTool(RustTool::kRsToolBin);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      rustc_tool.get());
+  rustc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_out_dir}}/{{crate_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(rustc_tool));
+
+  // SWIFT
+  std::unique_ptr<Tool> swift_tool = Tool::CreateTool(CTool::kCToolSwift);
+  SetCommandForTool(
+      "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
+      swift_tool.get());
+  swift_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{module_name}}.swiftmodule"));
+  swift_tool->set_partial_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{source_name_part}}.o"));
+  toolchain->SetTool(std::move(swift_tool));
+
+  // CDYLIB
+  std::unique_ptr<Tool> cdylib_tool = Tool::CreateTool(RustTool::kRsToolCDylib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      cdylib_tool.get());
+  cdylib_tool->set_output_prefix("lib");
+  cdylib_tool->set_default_output_extension(".so");
+  cdylib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(cdylib_tool));
+
+  // DYLIB
+  std::unique_ptr<Tool> dylib_tool = Tool::CreateTool(RustTool::kRsToolDylib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      dylib_tool.get());
+  dylib_tool->set_output_prefix("lib");
+  dylib_tool->set_default_output_extension(".so");
+  dylib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(dylib_tool));
+
+  // RUST_PROC_MACRO
+  std::unique_ptr<Tool> rust_proc_macro_tool =
+      Tool::CreateTool(RustTool::kRsToolMacro);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      rust_proc_macro_tool.get());
+  rust_proc_macro_tool->set_output_prefix("lib");
+  rust_proc_macro_tool->set_default_output_extension(".so");
+  rust_proc_macro_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(rust_proc_macro_tool));
+
+  // RLIB
+  std::unique_ptr<Tool> rlib_tool = Tool::CreateTool(RustTool::kRsToolRlib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      rlib_tool.get());
+  rlib_tool->set_output_prefix("lib");
+  rlib_tool->set_default_output_extension(".rlib");
+  rlib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(rlib_tool));
+
+  // STATICLIB
+  std::unique_ptr<Tool> staticlib_tool =
+      Tool::CreateTool(RustTool::kRsToolStaticlib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      staticlib_tool.get());
+  staticlib_tool->set_output_prefix("lib");
+  staticlib_tool->set_default_output_extension(".a");
+  staticlib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(staticlib_tool));
+
+  toolchain->ToolchainSetupComplete();
+}
+
+// static
+void TestWithScope::SetCommandForTool(const std::string& cmd, Tool* tool) {
+  Err err;
+  SubstitutionPattern command;
+  command.Parse(cmd, nullptr, &err);
+  CHECK(!err.has_error()) << "Couldn't parse \"" << cmd << "\", "
+                          << "got " << err.message();
+  tool->set_command(command);
+}
+
+void TestWithScope::AppendPrintOutput(const std::string& str) {
+  print_output_.append(str);
+}
+
+TestParseInput::TestParseInput(const std::string& input)
+    : input_file_(SourceFile("//test")) {
+  input_file_.SetContents(input);
+
+  tokens_ = Tokenizer::Tokenize(&input_file_, &parse_err_);
+  if (!parse_err_.has_error())
+    parsed_ = Parser::Parse(tokens_, &parse_err_);
+}
+
+TestParseInput::~TestParseInput() = default;
+
+TestTarget::TestTarget(const TestWithScope& setup,
+                       const std::string& label_string,
+                       Target::OutputType type)
+    : Target(setup.settings(), setup.ParseLabel(label_string)) {
+  visibility().SetPublic();
+  set_output_type(type);
+  SetToolchain(setup.toolchain());
+}
+
+TestTarget::~TestTarget() = default;
diff --git a/src/gn/test_with_scope.h b/src/gn/test_with_scope.h
new file mode 100644 (file)
index 0000000..19854db
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TEST_WITH_SCOPE_H_
+#define TOOLS_GN_TEST_WITH_SCOPE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/build_settings.h"
+#include "gn/c_tool.h"
+#include "gn/err.h"
+#include "gn/general_tool.h"
+#include "gn/input_file.h"
+#include "gn/parse_tree.h"
+#include "gn/rust_tool.h"
+#include "gn/scope.h"
+#include "gn/scope_per_file_provider.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+#include "gn/token.h"
+#include "gn/toolchain.h"
+#include "gn/value.h"
+
+// A helper class for setting up a Scope that a test can use. It makes a
+// toolchain and sets up all the build state.
+class TestWithScope {
+ public:
+  TestWithScope();
+  ~TestWithScope();
+
+  BuildSettings* build_settings() { return &build_settings_; }
+  Settings* settings() { return &settings_; }
+  const Settings* settings() const { return &settings_; }
+  Toolchain* toolchain() { return &toolchain_; }
+  const Toolchain* toolchain() const { return &toolchain_; }
+  Scope* scope() { return &scope_; }
+  const Scope::ItemVector& items() { return items_; }
+
+  // This buffer accumulates output from any print() commands executed in the
+  // context of this test. Note that the implementation of this is not
+  // threadsafe so don't write tests that call print from multiple threads.
+  std::string& print_output() { return print_output_; }
+
+  // Parse the given string into a label in the default toolchain. This will
+  // assert if the label isn't valid (this is intended for hardcoded labels).
+  Label ParseLabel(const std::string& str) const;
+
+  // Parses, evaluates, and resolves targets from the given snippet of code.
+  // All targets must be defined in dependency order (does not use a Builder,
+  // just blindly resolves all targets in order).
+  bool ExecuteSnippet(const std::string& str, Err* err);
+
+  Value ExecuteExpression(const std::string& expr, Err* err);
+
+  // Fills in the tools for the given toolchain with reasonable default values.
+  // The toolchain in this object will be automatically set up with this
+  // function, it is exposed to allow tests to get the same functionality for
+  // other toolchains they make.
+  // Two slightly different toolchains can be generated by this function,
+  // based on the use_toc argument. This is only currently required by
+  // one test.
+  static void SetupToolchain(Toolchain* toolchain, bool use_toc = false);
+
+  // Sets the given text command on the given tool, parsing it as a
+  // substitution pattern. This will assert if the input is malformed. This is
+  // designed to help setting up Tools for tests.
+  static void SetCommandForTool(const std::string& cmd, Tool* tool);
+
+ private:
+  void AppendPrintOutput(const std::string& str);
+
+  BuildSettings build_settings_;
+  Settings settings_;
+  Toolchain toolchain_;
+  Scope scope_;
+  Scope::ItemVector items_;
+
+  // Supplies the scope with built-in variables like root_out_dir.
+  ScopePerFileProvider scope_progammatic_provider_;
+
+  std::string print_output_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestWithScope);
+};
+
+// Helper class to treat some string input as a file.
+//
+// Instantiate it with the contents you want, be sure to check for error, and
+// then you can execute the ParseNode or whatever.
+class TestParseInput {
+ public:
+  explicit TestParseInput(const std::string& input);
+  ~TestParseInput();
+
+  // Indicates whether and what error occurred during tokenizing and parsing.
+  bool has_error() const { return parse_err_.has_error(); }
+  const Err& parse_err() const { return parse_err_; }
+
+  const InputFile& input_file() const { return input_file_; }
+  const std::vector<Token>& tokens() const { return tokens_; }
+  const ParseNode* parsed() const { return parsed_.get(); }
+
+ private:
+  InputFile input_file_;
+
+  std::vector<Token> tokens_;
+  std::unique_ptr<ParseNode> parsed_;
+
+  Err parse_err_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestParseInput);
+};
+
+// Shortcut for creating targets for tests that take the test setup, a pretty-
+// style label, and a target type and sets everything up. The target will
+// default to public visibility.
+class TestTarget : public Target {
+ public:
+  TestTarget(const TestWithScope& setup,
+             const std::string& label_string,
+             Target::OutputType type);
+  ~TestTarget() override;
+};
+
+#endif  // TOOLS_GN_TEST_WITH_SCOPE_H_
diff --git a/src/gn/token.cc b/src/gn/token.cc
new file mode 100644 (file)
index 0000000..3af9c48
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/token.h"
+
+#include "base/logging.h"
+#include "gn/tokenizer.h"
+
+Token::Token() : type_(INVALID), value_() {}
+
+Token::Token(const Location& location, Type t, const std::string_view& v)
+    : type_(t), value_(v), location_(location) {}
+
+// static
+Token Token::ClassifyAndMake(const Location& location,
+                             const std::string_view& v) {
+  char first = v.size() > 0 ? v[0] : '\0';
+  char second = v.size() > 1 ? v[1] : '\0';
+  return Token(location, Tokenizer::ClassifyToken(first, second), v);
+}
+
+bool Token::IsIdentifierEqualTo(const char* v) const {
+  return type_ == IDENTIFIER && value_ == v;
+}
+
+bool Token::IsStringEqualTo(const char* v) const {
+  return type_ == STRING && value_ == v;
+}
diff --git a/src/gn/token.h b/src/gn/token.h
new file mode 100644 (file)
index 0000000..a11807a
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TOKEN_H_
+#define TOOLS_GN_TOKEN_H_
+
+#include <string_view>
+
+#include "gn/location.h"
+
+class Token {
+ public:
+  enum Type {
+    INVALID,
+    INTEGER,     // 123
+    STRING,      // "blah"
+    TRUE_TOKEN,  // Not "TRUE" to avoid collisions with #define in windows.h.
+    FALSE_TOKEN,
+
+    // Various operators.
+    EQUAL,
+    PLUS,
+    MINUS,
+    PLUS_EQUALS,
+    MINUS_EQUALS,
+    EQUAL_EQUAL,
+    NOT_EQUAL,
+    LESS_EQUAL,
+    GREATER_EQUAL,
+    LESS_THAN,
+    GREATER_THAN,
+    BOOLEAN_AND,
+    BOOLEAN_OR,
+    BANG,
+    DOT,
+
+    LEFT_PAREN,
+    RIGHT_PAREN,
+    LEFT_BRACKET,
+    RIGHT_BRACKET,
+    LEFT_BRACE,
+    RIGHT_BRACE,
+
+    IF,
+    ELSE,
+    IDENTIFIER,            // foo
+    COMMA,                 // ,
+    UNCLASSIFIED_COMMENT,  // #...\n, of unknown style (will be converted
+                           // to one below)
+    LINE_COMMENT,          // #...\n on a line alone.
+    SUFFIX_COMMENT,        // #...\n on a line following other code.
+    BLOCK_COMMENT,         // #...\n line comment, but free-standing.
+
+    UNCLASSIFIED_OPERATOR,
+
+    NUM_TYPES
+  };
+
+  Token();
+  Token(const Location& location, Type t, const std::string_view& v);
+
+  static Token ClassifyAndMake(const Location& location,
+                               const std::string_view& v);
+
+  Type type() const { return type_; }
+  const std::string_view& value() const { return value_; }
+  const Location& location() const { return location_; }
+  void set_location(Location location) { location_ = location; }
+  LocationRange range() const {
+    return LocationRange(
+        location_,
+        Location(location_.file(), location_.line_number(),
+                 location_.column_number() + static_cast<int>(value_.size()),
+                 location_.byte() + static_cast<int>(value_.size())));
+  }
+
+  // Helper functions for comparing this token to something.
+  bool IsIdentifierEqualTo(const char* v) const;
+  bool IsStringEqualTo(const char* v) const;
+
+ private:
+  Type type_;
+  std::string_view value_;
+  Location location_;
+};
+
+#endif  // TOOLS_GN_TOKEN_H_
diff --git a/src/gn/tokenizer.cc b/src/gn/tokenizer.cc
new file mode 100644 (file)
index 0000000..93a6425
--- /dev/null
@@ -0,0 +1,417 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/tokenizer.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "gn/input_file.h"
+
+namespace {
+
+bool CouldBeTwoCharOperatorBegin(char c) {
+  return c == '<' || c == '>' || c == '!' || c == '=' || c == '-' || c == '+' ||
+         c == '|' || c == '&';
+}
+
+bool CouldBeTwoCharOperatorEnd(char c) {
+  return c == '=' || c == '|' || c == '&';
+}
+
+bool CouldBeOneCharOperator(char c) {
+  return c == '=' || c == '<' || c == '>' || c == '+' || c == '!' || c == ':' ||
+         c == '|' || c == '&' || c == '-';
+}
+
+bool CouldBeOperator(char c) {
+  return CouldBeOneCharOperator(c) || CouldBeTwoCharOperatorBegin(c);
+}
+
+bool IsScoperChar(char c) {
+  return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}';
+}
+
+Token::Type GetSpecificOperatorType(std::string_view value) {
+  if (value == "=")
+    return Token::EQUAL;
+  if (value == "+")
+    return Token::PLUS;
+  if (value == "-")
+    return Token::MINUS;
+  if (value == "+=")
+    return Token::PLUS_EQUALS;
+  if (value == "-=")
+    return Token::MINUS_EQUALS;
+  if (value == "==")
+    return Token::EQUAL_EQUAL;
+  if (value == "!=")
+    return Token::NOT_EQUAL;
+  if (value == "<=")
+    return Token::LESS_EQUAL;
+  if (value == ">=")
+    return Token::GREATER_EQUAL;
+  if (value == "<")
+    return Token::LESS_THAN;
+  if (value == ">")
+    return Token::GREATER_THAN;
+  if (value == "&&")
+    return Token::BOOLEAN_AND;
+  if (value == "||")
+    return Token::BOOLEAN_OR;
+  if (value == "!")
+    return Token::BANG;
+  if (value == ".")
+    return Token::DOT;
+  return Token::INVALID;
+}
+
+}  // namespace
+
+Tokenizer::Tokenizer(const InputFile* input_file,
+                     Err* err,
+                     WhitespaceTransform whitespace_transform)
+    : input_file_(input_file),
+      input_(input_file->contents()),
+      err_(err),
+      whitespace_transform_(whitespace_transform) {}
+
+Tokenizer::~Tokenizer() = default;
+
+// static
+std::vector<Token> Tokenizer::Tokenize(
+    const InputFile* input_file,
+    Err* err,
+    WhitespaceTransform whitespace_transform) {
+  Tokenizer t(input_file, err, whitespace_transform);
+  return t.Run();
+}
+
+std::vector<Token> Tokenizer::Run() {
+  DCHECK(tokens_.empty());
+  while (!done()) {
+    AdvanceToNextToken();
+    if (done())
+      break;
+    Location location = GetCurrentLocation();
+
+    Token::Type type = ClassifyCurrent();
+    if (type == Token::INVALID) {
+      *err_ = GetErrorForInvalidToken(location);
+      break;
+    }
+    size_t token_begin = cur_;
+    AdvanceToEndOfToken(location, type);
+    if (has_error())
+      break;
+    size_t token_end = cur_;
+
+    std::string_view token_value(&input_.data()[token_begin],
+                                 token_end - token_begin);
+
+    if (type == Token::UNCLASSIFIED_OPERATOR) {
+      type = GetSpecificOperatorType(token_value);
+    } else if (type == Token::IDENTIFIER) {
+      if (token_value == "if")
+        type = Token::IF;
+      else if (token_value == "else")
+        type = Token::ELSE;
+      else if (token_value == "true")
+        type = Token::TRUE_TOKEN;
+      else if (token_value == "false")
+        type = Token::FALSE_TOKEN;
+    } else if (type == Token::UNCLASSIFIED_COMMENT) {
+      if (AtStartOfLine(token_begin) &&
+          // If it's a standalone comment, but is a continuation of a comment on
+          // a previous line, then instead make it a continued suffix comment.
+          (tokens_.empty() || tokens_.back().type() != Token::SUFFIX_COMMENT ||
+           tokens_.back().location().line_number() + 1 !=
+               location.line_number() ||
+           tokens_.back().location().column_number() !=
+               location.column_number())) {
+        type = Token::LINE_COMMENT;
+        if (!at_end())  // Could be EOF.
+          Advance();    // The current \n.
+        // If this comment is separated from the next syntax element, then we
+        // want to tag it as a block comment. This will become a standalone
+        // statement at the parser level to keep this comment separate, rather
+        // than attached to the subsequent statement.
+        while (!at_end() && IsCurrentWhitespace()) {
+          if (IsCurrentNewline()) {
+            type = Token::BLOCK_COMMENT;
+            break;
+          }
+          Advance();
+        }
+      } else {
+        type = Token::SUFFIX_COMMENT;
+      }
+    }
+
+    tokens_.push_back(Token(location, type, token_value));
+  }
+  if (err_->has_error())
+    tokens_.clear();
+  return tokens_;
+}
+
+// static
+size_t Tokenizer::ByteOffsetOfNthLine(const std::string_view& buf, int n) {
+  DCHECK_GT(n, 0);
+
+  if (n == 1)
+    return 0;
+
+  int cur_line = 1;
+  size_t cur_byte = 0;
+  while (cur_byte < buf.size()) {
+    if (IsNewline(buf, cur_byte)) {
+      cur_line++;
+      if (cur_line == n)
+        return cur_byte + 1;
+    }
+    cur_byte++;
+  }
+  return static_cast<size_t>(-1);
+}
+
+// static
+bool Tokenizer::IsNewline(const std::string_view& buffer, size_t offset) {
+  DCHECK(offset < buffer.size());
+  // We may need more logic here to handle different line ending styles.
+  return buffer[offset] == '\n';
+}
+
+// static
+bool Tokenizer::IsIdentifierFirstChar(char c) {
+  return base::IsAsciiAlpha(c) || c == '_';
+}
+
+// static
+bool Tokenizer::IsIdentifierContinuingChar(char c) {
+  // Also allow digits after the first char.
+  return IsIdentifierFirstChar(c) || base::IsAsciiDigit(c);
+}
+
+void Tokenizer::AdvanceToNextToken() {
+  while (!at_end() && IsCurrentWhitespace())
+    Advance();
+}
+
+// static
+Token::Type Tokenizer::ClassifyToken(char next_char, char following_char) {
+  if (base::IsAsciiDigit(next_char))
+    return Token::INTEGER;
+  if (next_char == '"')
+    return Token::STRING;
+
+  // Note: '-' handled specially below.
+  if (next_char != '-' && CouldBeOperator(next_char))
+    return Token::UNCLASSIFIED_OPERATOR;
+
+  if (IsIdentifierFirstChar(next_char))
+    return Token::IDENTIFIER;
+
+  if (next_char == '[')
+    return Token::LEFT_BRACKET;
+  if (next_char == ']')
+    return Token::RIGHT_BRACKET;
+  if (next_char == '(')
+    return Token::LEFT_PAREN;
+  if (next_char == ')')
+    return Token::RIGHT_PAREN;
+  if (next_char == '{')
+    return Token::LEFT_BRACE;
+  if (next_char == '}')
+    return Token::RIGHT_BRACE;
+
+  if (next_char == '.')
+    return Token::DOT;
+  if (next_char == ',')
+    return Token::COMMA;
+
+  if (next_char == '#')
+    return Token::UNCLASSIFIED_COMMENT;
+
+  // For the case of '-' differentiate between a negative number and anything
+  // else.
+  if (next_char == '-') {
+    if (following_char == '\0')
+      return Token::UNCLASSIFIED_OPERATOR;  // Just the minus before end of
+                                            // file.
+    if (base::IsAsciiDigit(following_char))
+      return Token::INTEGER;
+    return Token::UNCLASSIFIED_OPERATOR;
+  }
+
+  return Token::INVALID;
+}
+
+Token::Type Tokenizer::ClassifyCurrent() const {
+  DCHECK(!at_end());
+  char next_char = cur_char();
+  char following_char = CanIncrement() ? input_[cur_ + 1] : '\0';
+  return ClassifyToken(next_char, following_char);
+}
+
+void Tokenizer::AdvanceToEndOfToken(const Location& location,
+                                    Token::Type type) {
+  switch (type) {
+    case Token::INTEGER:
+      do {
+        Advance();
+      } while (!at_end() && base::IsAsciiDigit(cur_char()));
+      if (!at_end()) {
+        // Require the char after a number to be some kind of space, scope,
+        // or operator.
+        char c = cur_char();
+        if (!IsCurrentWhitespace() && !CouldBeOperator(c) && !IsScoperChar(c) &&
+            c != ',') {
+          *err_ = Err(GetCurrentLocation(), "This is not a valid number.");
+          // Highlight the number.
+          err_->AppendRange(LocationRange(location, GetCurrentLocation()));
+        }
+      }
+      break;
+
+    case Token::STRING: {
+      char initial = cur_char();
+      Advance();  // Advance past initial "
+      for (;;) {
+        if (at_end()) {
+          *err_ = Err(LocationRange(location, GetCurrentLocation()),
+                      "Unterminated string literal.",
+                      "Don't leave me hanging like this!");
+          break;
+        }
+        if (IsCurrentStringTerminator(initial)) {
+          Advance();  // Skip past last "
+          break;
+        } else if (IsCurrentNewline()) {
+          *err_ = Err(LocationRange(location, GetCurrentLocation()),
+                      "Newline in string constant.");
+        }
+        Advance();
+      }
+      break;
+    }
+
+    case Token::UNCLASSIFIED_OPERATOR:
+      // Some operators are two characters, some are one.
+      if (CouldBeTwoCharOperatorBegin(cur_char())) {
+        if (CanIncrement() && CouldBeTwoCharOperatorEnd(input_[cur_ + 1]))
+          Advance();
+      }
+      Advance();
+      break;
+
+    case Token::IDENTIFIER:
+      while (!at_end() && IsIdentifierContinuingChar(cur_char()))
+        Advance();
+      break;
+
+    case Token::LEFT_BRACKET:
+    case Token::RIGHT_BRACKET:
+    case Token::LEFT_BRACE:
+    case Token::RIGHT_BRACE:
+    case Token::LEFT_PAREN:
+    case Token::RIGHT_PAREN:
+    case Token::DOT:
+    case Token::COMMA:
+      Advance();  // All are one char.
+      break;
+
+    case Token::UNCLASSIFIED_COMMENT:
+      // Eat to EOL.
+      while (!at_end() && !IsCurrentNewline())
+        Advance();
+      break;
+
+    case Token::INVALID:
+    default:
+      *err_ = Err(location, "Everything is all messed up",
+                  "Please insert system disk in drive A: and press any key.");
+      NOTREACHED();
+      return;
+  }
+}
+
+bool Tokenizer::AtStartOfLine(size_t location) const {
+  while (location > 0) {
+    --location;
+    char c = input_[location];
+    if (c == '\n')
+      return true;
+    if (c != ' ')
+      return false;
+  }
+  return true;
+}
+
+bool Tokenizer::IsCurrentWhitespace() const {
+  DCHECK(!at_end());
+  char c = input_[cur_];
+  // Note that tab (0x09), vertical tab (0x0B), and formfeed (0x0C) are illegal.
+  return c == 0x0A || c == 0x0D || c == 0x20 ||
+         (whitespace_transform_ == WhitespaceTransform::kInvalidToSpace &&
+          (c == 0x09 || c == 0x0B || c == 0x0C));
+}
+
+bool Tokenizer::IsCurrentStringTerminator(char quote_char) const {
+  DCHECK(!at_end());
+  if (cur_char() != quote_char)
+    return false;
+
+  // Check for escaping. \" is not a string terminator, but \\" is. Count
+  // the number of preceding backslashes.
+  int num_backslashes = 0;
+  for (int i = static_cast<int>(cur_) - 1; i >= 0 && input_[i] == '\\'; i--)
+    num_backslashes++;
+
+  // Even backslashes mean that they were escaping each other and don't count
+  // as escaping this quote.
+  return (num_backslashes % 2) == 0;
+}
+
+bool Tokenizer::IsCurrentNewline() const {
+  return IsNewline(input_, cur_);
+}
+
+void Tokenizer::Advance() {
+  DCHECK(cur_ < input_.size());
+  if (IsCurrentNewline()) {
+    line_number_++;
+    column_number_ = 1;
+  } else {
+    column_number_++;
+  }
+  cur_++;
+}
+
+Location Tokenizer::GetCurrentLocation() const {
+  return Location(input_file_, line_number_, column_number_,
+                  static_cast<int>(cur_));
+}
+
+Err Tokenizer::GetErrorForInvalidToken(const Location& location) const {
+  std::string help;
+  if (cur_char() == ';') {
+    // Semicolon.
+    help = "Semicolons are not needed, delete this one.";
+  } else if (cur_char() == '\t') {
+    // Tab.
+    help =
+        "You got a tab character in here. Tabs are evil. "
+        "Convert to spaces.";
+  } else if (cur_char() == '/' && cur_ + 1 < input_.size() &&
+             (input_[cur_ + 1] == '/' || input_[cur_ + 1] == '*')) {
+    // Different types of comments.
+    help = "Comments should start with # instead";
+  } else if (cur_char() == '\'') {
+    help = "Strings are delimited by \" characters, not apostrophes.";
+  } else {
+    help = "I have no idea what this is.";
+  }
+
+  return Err(location, "Invalid token.", help);
+}
diff --git a/src/gn/tokenizer.h b/src/gn/tokenizer.h
new file mode 100644 (file)
index 0000000..d4b347b
--- /dev/null
@@ -0,0 +1,108 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TOKENIZER_H_
+#define TOOLS_GN_TOKENIZER_H_
+
+#include <stddef.h>
+
+#include <string_view>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/err.h"
+#include "gn/token.h"
+
+class InputFile;
+
+// Tab (0x09), vertical tab (0x0B), and formfeed (0x0C) are illegal in GN files.
+// Almost always these are errors. However, in the case of running the formatter
+// it's nice to convert these to spaces when encountered so that the input can
+// still be parsed and rewritten correctly by the formatter.
+enum class WhitespaceTransform {
+  kMaintainOriginalInput,
+  kInvalidToSpace,
+};
+
+class Tokenizer {
+ public:
+  static std::vector<Token> Tokenize(
+      const InputFile* input_file,
+      Err* err,
+      WhitespaceTransform whitespace_transform =
+          WhitespaceTransform::kMaintainOriginalInput);
+
+  // Counts lines in the given buffer (the first line is "1") and returns
+  // the byte offset of the beginning of that line, or (size_t)-1 if there
+  // aren't that many lines in the file. Note that this will return the byte
+  // one past the end of the input if the last character is a newline.
+  //
+  // This is a helper function for error output so that the tokenizer's
+  // notion of lines can be used elsewhere.
+  static size_t ByteOffsetOfNthLine(const std::string_view& buf, int n);
+
+  // Returns true if the given offset of the string piece counts as a newline.
+  // The offset must be in the buffer.
+  static bool IsNewline(const std::string_view& buffer, size_t offset);
+
+  static bool IsIdentifierFirstChar(char c);
+
+  static bool IsIdentifierContinuingChar(char c);
+
+  static Token::Type ClassifyToken(char next_char, char following_char);
+
+ private:
+  // InputFile must outlive the tokenizer and all generated tokens.
+  Tokenizer(const InputFile* input_file,
+            Err* err,
+            WhitespaceTransform whitespace_transform);
+  ~Tokenizer();
+
+  std::vector<Token> Run();
+
+  void AdvanceToNextToken();
+  Token::Type ClassifyCurrent() const;
+  void AdvanceToEndOfToken(const Location& location, Token::Type type);
+
+  // Whether from this location back to the beginning of the line is only
+  // whitespace. |location| should be the first character of the token to be
+  // checked.
+  bool AtStartOfLine(size_t location) const;
+
+  bool IsCurrentWhitespace() const;
+  bool IsCurrentNewline() const;
+  bool IsCurrentStringTerminator(char quote_char) const;
+
+  bool CanIncrement() const { return cur_ < input_.size() - 1; }
+
+  // Increments the current location by one.
+  void Advance();
+
+  // Returns the current character in the file as a location.
+  Location GetCurrentLocation() const;
+
+  Err GetErrorForInvalidToken(const Location& location) const;
+
+  bool done() const { return at_end() || has_error(); }
+
+  bool at_end() const { return cur_ == input_.size(); }
+  char cur_char() const { return input_[cur_]; }
+
+  bool has_error() const { return err_->has_error(); }
+
+  std::vector<Token> tokens_;
+
+  const InputFile* input_file_;
+  const std::string_view input_;
+  Err* err_;
+  WhitespaceTransform whitespace_transform_;
+  size_t cur_ = 0;  // Byte offset into input buffer.
+
+  int line_number_ = 1;
+  int column_number_ = 1;
+
+  DISALLOW_COPY_AND_ASSIGN(Tokenizer);
+};
+
+#endif  // TOOLS_GN_TOKENIZER_H_
diff --git a/src/gn/tokenizer_unittest.cc b/src/gn/tokenizer_unittest.cc
new file mode 100644 (file)
index 0000000..d740d86
--- /dev/null
@@ -0,0 +1,237 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "gn/input_file.h"
+#include "gn/token.h"
+#include "gn/tokenizer.h"
+#include "util/test/test.h"
+
+namespace {
+
+struct TokenExpectation {
+  Token::Type type;
+  const char* value;
+};
+
+template <size_t len>
+bool CheckTokenizer(const char* input, const TokenExpectation (&expect)[len]) {
+  InputFile input_file(SourceFile("/test"));
+  input_file.SetContents(input);
+
+  Err err;
+  std::vector<Token> results = Tokenizer::Tokenize(&input_file, &err);
+
+  if (results.size() != len)
+    return false;
+  for (size_t i = 0; i < len; i++) {
+    if (expect[i].type != results[i].type())
+      return false;
+    if (expect[i].value != results[i].value())
+      return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+TEST(Tokenizer, Empty) {
+  InputFile empty_string_input(SourceFile("/test"));
+  empty_string_input.SetContents("");
+
+  Err err;
+  std::vector<Token> results = Tokenizer::Tokenize(&empty_string_input, &err);
+  EXPECT_TRUE(results.empty());
+
+  InputFile whitespace_input(SourceFile("/test"));
+  whitespace_input.SetContents("  \r \n \r\n");
+
+  results = Tokenizer::Tokenize(&whitespace_input, &err);
+  EXPECT_TRUE(results.empty());
+}
+
+TEST(Tokenizer, Identifier) {
+  TokenExpectation one_ident[] = {{Token::IDENTIFIER, "foo"}};
+  EXPECT_TRUE(CheckTokenizer("  foo ", one_ident));
+}
+
+TEST(Tokenizer, Integer) {
+  TokenExpectation integers[] = {{Token::INTEGER, "123"},
+                                 {Token::INTEGER, "-123"}};
+  EXPECT_TRUE(CheckTokenizer("  123 -123 ", integers));
+}
+
+TEST(Tokenizer, IntegerNoSpace) {
+  TokenExpectation integers[] = {{Token::INTEGER, "123"},
+                                 {Token::INTEGER, "-123"}};
+  EXPECT_TRUE(CheckTokenizer("  123-123 ", integers));
+}
+
+TEST(Tokenizer, String) {
+  TokenExpectation strings[] = {{Token::STRING, "\"foo\""},
+                                {Token::STRING, "\"bar\\\"baz\""},
+                                {Token::STRING, "\"asdf\\\\\""}};
+  EXPECT_TRUE(
+      CheckTokenizer("  \"foo\" \"bar\\\"baz\" \"asdf\\\\\" ", strings));
+}
+
+TEST(Tokenizer, Operator) {
+  TokenExpectation operators[] = {
+      {Token::MINUS, "-"},
+      {Token::PLUS, "+"},
+      {Token::EQUAL, "="},
+      {Token::PLUS_EQUALS, "+="},
+      {Token::MINUS_EQUALS, "-="},
+      {Token::NOT_EQUAL, "!="},
+      {Token::EQUAL_EQUAL, "=="},
+      {Token::LESS_THAN, "<"},
+      {Token::GREATER_THAN, ">"},
+      {Token::LESS_EQUAL, "<="},
+      {Token::GREATER_EQUAL, ">="},
+      {Token::BANG, "!"},
+      {Token::BOOLEAN_OR, "||"},
+      {Token::BOOLEAN_AND, "&&"},
+      {Token::DOT, "."},
+      {Token::COMMA, ","},
+  };
+  EXPECT_TRUE(
+      CheckTokenizer("- + = += -= != ==  < > <= >= ! || && . ,", operators));
+}
+
+TEST(Tokenizer, Scoper) {
+  TokenExpectation scopers[] = {
+      {Token::LEFT_BRACE, "{"},    {Token::LEFT_BRACKET, "["},
+      {Token::RIGHT_BRACKET, "]"}, {Token::RIGHT_BRACE, "}"},
+      {Token::LEFT_PAREN, "("},    {Token::RIGHT_PAREN, ")"},
+  };
+  EXPECT_TRUE(CheckTokenizer("{[ ]} ()", scopers));
+}
+
+TEST(Tokenizer, FunctionCall) {
+  TokenExpectation fn[] = {
+      {Token::IDENTIFIER, "fun"}, {Token::LEFT_PAREN, "("},
+      {Token::STRING, "\"foo\""}, {Token::RIGHT_PAREN, ")"},
+      {Token::LEFT_BRACE, "{"},   {Token::IDENTIFIER, "foo"},
+      {Token::EQUAL, "="},        {Token::INTEGER, "12"},
+      {Token::RIGHT_BRACE, "}"},
+  };
+  EXPECT_TRUE(CheckTokenizer("fun(\"foo\") {\nfoo = 12}", fn));
+}
+
+TEST(Tokenizer, Locations) {
+  InputFile input(SourceFile("/test"));
+  input.SetContents("1 2 \"three\"\n  4");
+  Err err;
+  std::vector<Token> results = Tokenizer::Tokenize(&input, &err);
+
+  ASSERT_EQ(4u, results.size());
+  ASSERT_TRUE(results[0].location() == Location(&input, 1, 1, 1));
+  ASSERT_TRUE(results[1].location() == Location(&input, 1, 3, 3));
+  ASSERT_TRUE(results[2].location() == Location(&input, 1, 5, 5));
+  ASSERT_TRUE(results[3].location() == Location(&input, 2, 3, 8));
+}
+
+TEST(Tokenizer, ByteOffsetOfNthLine) {
+  EXPECT_EQ(0u, Tokenizer::ByteOffsetOfNthLine("foo", 1));
+
+  // Windows and Posix have different line endings, so check the byte at the
+  // location rather than the offset.
+  char input1[] = "aaa\nxaa\n\nya";
+  EXPECT_EQ('x', input1[Tokenizer::ByteOffsetOfNthLine(input1, 2)]);
+  EXPECT_EQ('y', input1[Tokenizer::ByteOffsetOfNthLine(input1, 4)]);
+
+  char input2[3];
+  input2[0] = 'a';
+  input2[1] = '\n';  // Manually set to avoid Windows double-byte endings.
+  input2[2] = 0;
+  EXPECT_EQ(0u, Tokenizer::ByteOffsetOfNthLine(input2, 1));
+  EXPECT_EQ(2u, Tokenizer::ByteOffsetOfNthLine(input2, 2));
+}
+
+TEST(Tokenizer, Comments) {
+  TokenExpectation fn[] = {
+      {Token::LINE_COMMENT, "# Stuff"},
+      {Token::IDENTIFIER, "fun"},
+      {Token::LEFT_PAREN, "("},
+      {Token::STRING, "\"foo\""},
+      {Token::RIGHT_PAREN, ")"},
+      {Token::LEFT_BRACE, "{"},
+      {Token::SUFFIX_COMMENT, "# Things"},
+      {Token::LINE_COMMENT, "#Wee"},
+      {Token::IDENTIFIER, "foo"},
+      {Token::EQUAL, "="},
+      {Token::INTEGER, "12"},
+      {Token::SUFFIX_COMMENT, "#Zip"},
+      {Token::RIGHT_BRACE, "}"},
+  };
+  EXPECT_TRUE(
+      CheckTokenizer("# Stuff\n"
+                     "fun(\"foo\") {  # Things\n"
+                     "#Wee\n"
+                     "foo = 12 #Zip\n"
+                     "}",
+                     fn));
+}
+
+TEST(Tokenizer, CommentsContinued) {
+  // In the first test, the comments aren't horizontally aligned, so they're
+  // considered separate. In the second test, they are, so "B" is a
+  // continuation of "A" (another SUFFIX comment).
+  TokenExpectation fn1[] = {
+      {Token::IDENTIFIER, "fun"},   {Token::LEFT_PAREN, "("},
+      {Token::STRING, "\"foo\""},   {Token::RIGHT_PAREN, ")"},
+      {Token::LEFT_BRACE, "{"},     {Token::SUFFIX_COMMENT, "# A"},
+      {Token::LINE_COMMENT, "# B"}, {Token::RIGHT_BRACE, "}"},
+  };
+  EXPECT_TRUE(
+      CheckTokenizer("fun(\"foo\") {  # A\n"
+                     "  # B\n"
+                     "}",
+                     fn1));
+
+  TokenExpectation fn2[] = {
+      {Token::IDENTIFIER, "fun"},     {Token::LEFT_PAREN, "("},
+      {Token::STRING, "\"foo\""},     {Token::RIGHT_PAREN, ")"},
+      {Token::LEFT_BRACE, "{"},       {Token::SUFFIX_COMMENT, "# A"},
+      {Token::SUFFIX_COMMENT, "# B"}, {Token::RIGHT_BRACE, "}"},
+  };
+  EXPECT_TRUE(CheckTokenizer(
+      "fun(\"foo\") {  # A\n"
+      "              # B\n"  // Note that these are aligned, the \"s move A out.
+      "}",
+      fn2));
+}
+
+TEST(Tokenizer, WhitespaceTransformMaintain) {
+  InputFile input(SourceFile("/test"));
+  input.SetContents("a\t2\v\"st\tuff\"\f{");
+
+  Err err;
+  std::vector<Token> results = Tokenizer::Tokenize(
+      &input, &err, WhitespaceTransform::kMaintainOriginalInput);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(err.location().column_number(), 2);
+}
+
+TEST(Tokenizer, WhitespaceTransformToSpace) {
+  InputFile input(SourceFile("/test"));
+  input.SetContents("a\t2\v\"st\tuff\"\f{");
+
+  Err err;
+  std::vector<Token> results =
+      Tokenizer::Tokenize(&input, &err, WhitespaceTransform::kInvalidToSpace);
+  EXPECT_FALSE(err.has_error());
+  ASSERT_EQ(results.size(), 4u);
+  EXPECT_EQ(results[0].type(), Token::IDENTIFIER);
+  EXPECT_EQ(results[0].value(), "a");
+  EXPECT_EQ(results[1].type(), Token::INTEGER);
+  EXPECT_EQ(results[1].value(), "2");
+  EXPECT_EQ(results[2].type(), Token::STRING);
+  EXPECT_EQ(results[2].value(),
+            "\"st\tuff\"");  // Note, embedded \t not transformed.
+  EXPECT_EQ(results[3].type(), Token::LEFT_BRACE);
+  EXPECT_EQ(results[3].value(), "{");
+}
+
diff --git a/src/gn/tool.cc b/src/gn/tool.cc
new file mode 100644 (file)
index 0000000..18f2036
--- /dev/null
@@ -0,0 +1,407 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/tool.h"
+
+#include "gn/c_tool.h"
+#include "gn/general_tool.h"
+#include "gn/rust_tool.h"
+#include "gn/settings.h"
+#include "gn/target.h"
+
+const char* Tool::kToolNone = "";
+
+Tool::Tool(const char* n) : name_(n) {}
+
+Tool::~Tool() = default;
+
+void Tool::SetToolComplete() {
+  DCHECK(!complete_);
+  complete_ = true;
+
+  command_.FillRequiredTypes(&substitution_bits_);
+  depfile_.FillRequiredTypes(&substitution_bits_);
+  description_.FillRequiredTypes(&substitution_bits_);
+  outputs_.FillRequiredTypes(&substitution_bits_);
+  rspfile_.FillRequiredTypes(&substitution_bits_);
+  rspfile_content_.FillRequiredTypes(&substitution_bits_);
+  partial_outputs_.FillRequiredTypes(&substitution_bits_);
+}
+
+GeneralTool* Tool::AsGeneral() {
+  return nullptr;
+}
+
+const GeneralTool* Tool::AsGeneral() const {
+  return nullptr;
+}
+
+CTool* Tool::AsC() {
+  return nullptr;
+}
+
+const CTool* Tool::AsC() const {
+  return nullptr;
+}
+
+RustTool* Tool::AsRust() {
+  return nullptr;
+}
+const RustTool* Tool::AsRust() const {
+  return nullptr;
+}
+
+bool Tool::IsPatternInOutputList(const SubstitutionList& output_list,
+                                 const SubstitutionPattern& pattern) const {
+  for (const auto& cur : output_list.list()) {
+    if (pattern.ranges().size() == cur.ranges().size() &&
+        std::equal(pattern.ranges().begin(), pattern.ranges().end(),
+                   cur.ranges().begin()))
+      return true;
+  }
+  return false;
+}
+
+bool Tool::ValidateSubstitutionList(
+    const std::vector<const Substitution*>& list,
+    const Value* origin,
+    Err* err) const {
+  for (const auto& cur_type : list) {
+    if (!ValidateSubstitution(cur_type)) {
+      *err = Err(*origin, "Pattern not valid here.",
+                 "You used the pattern " + std::string(cur_type->name) +
+                     " which is not valid\nfor this variable.");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Tool::ReadBool(Scope* scope, const char* var, bool* field, Err* err) {
+  DCHECK(!complete_);
+  const Value* v = scope->GetValue(var, true);
+  if (!v)
+    return true;  // Not present is fine.
+  if (!v->VerifyTypeIs(Value::BOOLEAN, err))
+    return false;
+  *field = v->boolean_value();
+  return true;
+}
+
+bool Tool::ReadString(Scope* scope,
+                      const char* var,
+                      std::string* field,
+                      Err* err) {
+  DCHECK(!complete_);
+  const Value* v = scope->GetValue(var, true);
+  if (!v)
+    return true;  // Not present is fine.
+  if (!v->VerifyTypeIs(Value::STRING, err))
+    return false;
+  *field = v->string_value();
+  return true;
+}
+
+bool Tool::ReadPattern(Scope* scope,
+                       const char* var,
+                       SubstitutionPattern* field,
+                       Err* err) {
+  DCHECK(!complete_);
+  const Value* value = scope->GetValue(var, true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::STRING, err))
+    return false;
+
+  SubstitutionPattern pattern;
+  if (!pattern.Parse(*value, err))
+    return false;
+  if (!ValidateSubstitutionList(pattern.required_types(), value, err))
+    return false;
+
+  *field = std::move(pattern);
+  return true;
+}
+
+bool Tool::ReadPatternList(Scope* scope,
+                           const char* var,
+                           SubstitutionList* field,
+                           Err* err) {
+  DCHECK(!complete_);
+  const Value* value = scope->GetValue(var, true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::LIST, err))
+    return false;
+
+  SubstitutionList list;
+  if (!list.Parse(*value, err))
+    return false;
+
+  // Validate the right kinds of patterns are used.
+  if (!ValidateSubstitutionList(list.required_types(), value, err))
+    return false;
+
+  *field = std::move(list);
+  return true;
+}
+
+bool Tool::ReadLabel(Scope* scope,
+                     const char* var,
+                     const Label& current_toolchain,
+                     LabelPtrPair<Pool>* field,
+                     Err* err) {
+  DCHECK(!complete_);
+  const Value* v = scope->GetValue(var, true);
+  if (!v)
+    return true;  // Not present is fine.
+
+  Label label =
+      Label::Resolve(scope->GetSourceDir(),
+                     scope->settings()->build_settings()->root_path_utf8(),
+                     current_toolchain, *v, err);
+  if (err->has_error())
+    return false;
+
+  LabelPtrPair<Pool> pair(label);
+  pair.origin = defined_from();
+
+  *field = std::move(pair);
+  return true;
+}
+
+bool Tool::ReadOutputExtension(Scope* scope, Err* err) {
+  DCHECK(!complete_);
+  const Value* value = scope->GetValue("default_output_extension", true);
+  if (!value)
+    return true;  // Not present is fine.
+  if (!value->VerifyTypeIs(Value::STRING, err))
+    return false;
+
+  if (value->string_value().empty())
+    return true;  // Accept empty string.
+
+  if (value->string_value()[0] != '.') {
+    *err = Err(*value, "default_output_extension must begin with a '.'");
+    return false;
+  }
+
+  set_default_output_extension(value->string_value());
+  return true;
+}
+
+bool Tool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+  if (!ReadPattern(scope, "command", &command_, err) ||
+      !ReadString(scope, "command_launcher", &command_launcher_, err) ||
+      !ReadOutputExtension(scope, err) ||
+      !ReadPattern(scope, "depfile", &depfile_, err) ||
+      !ReadPattern(scope, "description", &description_, err) ||
+      !ReadPatternList(scope, "runtime_outputs", &runtime_outputs_, err) ||
+      !ReadString(scope, "output_prefix", &output_prefix_, err) ||
+      !ReadPattern(scope, "default_output_dir", &default_output_dir_, err) ||
+      !ReadBool(scope, "restat", &restat_, err) ||
+      !ReadPattern(scope, "rspfile", &rspfile_, err) ||
+      !ReadPattern(scope, "rspfile_content", &rspfile_content_, err) ||
+      !ReadLabel(scope, "pool", toolchain->label(), &pool_, err)) {
+    return false;
+  }
+  const bool command_is_required = name_ != GeneralTool::kGeneralToolAction;
+  if (command_.empty() == command_is_required) {
+    *err = Err(defined_from(), "This tool's command is bad.",
+               command_is_required
+                   ? "This tool requires \"command\" to be defined."
+                   : "This tool doesn't support \"command\".");
+    return false;
+  }
+  return true;
+}
+
+std::unique_ptr<Tool> Tool::CreateTool(const ParseNode* function,
+                                       const std::string& name,
+                                       Scope* scope,
+                                       Toolchain* toolchain,
+                                       Err* err) {
+  std::unique_ptr<Tool> tool = CreateTool(name);
+  if (!tool) {
+    *err = Err(function, "Unknown tool type.");
+    return nullptr;
+  }
+  if (CTool* c_tool = tool->AsC()) {
+    if (c_tool->InitTool(scope, toolchain, err))
+      return tool;
+    return nullptr;
+  }
+  if (GeneralTool* general_tool = tool->AsGeneral()) {
+    if (general_tool->InitTool(scope, toolchain, err))
+      return tool;
+    return nullptr;
+  }
+  if (RustTool* rust_tool = tool->AsRust()) {
+    if (rust_tool->InitTool(scope, toolchain, err))
+      return tool;
+    return nullptr;
+  }
+  NOTREACHED();
+  *err = Err(function, "Unknown tool type.");
+  return nullptr;
+}
+
+// static
+std::unique_ptr<Tool> Tool::CreateTool(const std::string& name) {
+  // C tools
+  if (name == CTool::kCToolCc)
+    return std::make_unique<CTool>(CTool::kCToolCc);
+  else if (name == CTool::kCToolCxx)
+    return std::make_unique<CTool>(CTool::kCToolCxx);
+  else if (name == CTool::kCToolCxxModule)
+    return std::make_unique<CTool>(CTool::kCToolCxxModule);
+  else if (name == CTool::kCToolObjC)
+    return std::make_unique<CTool>(CTool::kCToolObjC);
+  else if (name == CTool::kCToolObjCxx)
+    return std::make_unique<CTool>(CTool::kCToolObjCxx);
+  else if (name == CTool::kCToolRc)
+    return std::make_unique<CTool>(CTool::kCToolRc);
+  else if (name == CTool::kCToolAsm)
+    return std::make_unique<CTool>(CTool::kCToolAsm);
+  else if (name == CTool::kCToolSwift)
+    return std::make_unique<CTool>(CTool::kCToolSwift);
+  else if (name == CTool::kCToolAlink)
+    return std::make_unique<CTool>(CTool::kCToolAlink);
+  else if (name == CTool::kCToolSolink)
+    return std::make_unique<CTool>(CTool::kCToolSolink);
+  else if (name == CTool::kCToolSolinkModule)
+    return std::make_unique<CTool>(CTool::kCToolSolinkModule);
+  else if (name == CTool::kCToolLink)
+    return std::make_unique<CTool>(CTool::kCToolLink);
+
+  // General tools
+  else if (name == GeneralTool::kGeneralToolAction)
+    return std::make_unique<GeneralTool>(GeneralTool::kGeneralToolAction);
+  else if (name == GeneralTool::kGeneralToolStamp)
+    return std::make_unique<GeneralTool>(GeneralTool::kGeneralToolStamp);
+  else if (name == GeneralTool::kGeneralToolCopy)
+    return std::make_unique<GeneralTool>(GeneralTool::kGeneralToolCopy);
+  else if (name == GeneralTool::kGeneralToolCopyBundleData)
+    return std::make_unique<GeneralTool>(
+        GeneralTool::kGeneralToolCopyBundleData);
+  else if (name == GeneralTool::kGeneralToolCompileXCAssets)
+    return std::make_unique<GeneralTool>(
+        GeneralTool::kGeneralToolCompileXCAssets);
+
+  // Rust tool
+  else if (name == RustTool::kRsToolBin)
+    return std::make_unique<RustTool>(RustTool::kRsToolBin);
+  else if (name == RustTool::kRsToolCDylib)
+    return std::make_unique<RustTool>(RustTool::kRsToolCDylib);
+  else if (name == RustTool::kRsToolDylib)
+    return std::make_unique<RustTool>(RustTool::kRsToolDylib);
+  else if (name == RustTool::kRsToolMacro)
+    return std::make_unique<RustTool>(RustTool::kRsToolMacro);
+  else if (name == RustTool::kRsToolRlib)
+    return std::make_unique<RustTool>(RustTool::kRsToolRlib);
+  else if (name == RustTool::kRsToolStaticlib)
+    return std::make_unique<RustTool>(RustTool::kRsToolStaticlib);
+
+  return nullptr;
+}
+
+// static
+const char* Tool::GetToolTypeForSourceType(SourceFile::Type type) {
+  switch (type) {
+    case SourceFile::SOURCE_C:
+      return CTool::kCToolCc;
+    case SourceFile::SOURCE_CPP:
+      return CTool::kCToolCxx;
+    case SourceFile::SOURCE_MODULEMAP:
+      return CTool::kCToolCxxModule;
+    case SourceFile::SOURCE_M:
+      return CTool::kCToolObjC;
+    case SourceFile::SOURCE_MM:
+      return CTool::kCToolObjCxx;
+    case SourceFile::SOURCE_ASM:
+    case SourceFile::SOURCE_S:
+      return CTool::kCToolAsm;
+    case SourceFile::SOURCE_RC:
+      return CTool::kCToolRc;
+    case SourceFile::SOURCE_RS:
+      return RustTool::kRsToolBin;
+    case SourceFile::SOURCE_SWIFT:
+      return CTool::kCToolSwift;
+    case SourceFile::SOURCE_UNKNOWN:
+    case SourceFile::SOURCE_H:
+    case SourceFile::SOURCE_O:
+    case SourceFile::SOURCE_DEF:
+    case SourceFile::SOURCE_GO:
+      return kToolNone;
+    default:
+      NOTREACHED();
+      return kToolNone;
+  }
+}
+
+// static
+const char* Tool::GetToolTypeForTargetFinalOutput(const Target* target) {
+  // The contents of this list might be surprising (i.e. stamp tool for copy
+  // rules). See the header for why.
+  // TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
+  if (target->source_types_used().RustSourceUsed()) {
+    switch (target->rust_values().crate_type()) {
+      case RustValues::CRATE_AUTO: {
+        switch (target->output_type()) {
+          case Target::EXECUTABLE:
+            return RustTool::kRsToolBin;
+          case Target::SHARED_LIBRARY:
+            return RustTool::kRsToolDylib;
+          case Target::STATIC_LIBRARY:
+            return RustTool::kRsToolStaticlib;
+          case Target::RUST_LIBRARY:
+            return RustTool::kRsToolRlib;
+          case Target::RUST_PROC_MACRO:
+            return RustTool::kRsToolMacro;
+          default:
+            break;
+        }
+        break;
+      }
+      case RustValues::CRATE_BIN:
+        return RustTool::kRsToolBin;
+      case RustValues::CRATE_CDYLIB:
+        return RustTool::kRsToolCDylib;
+      case RustValues::CRATE_DYLIB:
+        return RustTool::kRsToolDylib;
+      case RustValues::CRATE_PROC_MACRO:
+        return RustTool::kRsToolMacro;
+      case RustValues::CRATE_RLIB:
+        return RustTool::kRsToolRlib;
+      case RustValues::CRATE_STATICLIB:
+        return RustTool::kRsToolStaticlib;
+      default:
+        NOTREACHED();
+    }
+  }
+  switch (target->output_type()) {
+    case Target::GROUP:
+      return GeneralTool::kGeneralToolStamp;
+    case Target::EXECUTABLE:
+      return CTool::kCToolLink;
+    case Target::SHARED_LIBRARY:
+      return CTool::kCToolSolink;
+    case Target::LOADABLE_MODULE:
+      return CTool::kCToolSolinkModule;
+    case Target::STATIC_LIBRARY:
+      return CTool::kCToolAlink;
+    case Target::SOURCE_SET:
+      return GeneralTool::kGeneralToolStamp;
+    case Target::ACTION:
+    case Target::ACTION_FOREACH:
+    case Target::BUNDLE_DATA:
+    case Target::CREATE_BUNDLE:
+    case Target::COPY_FILES:
+    case Target::GENERATED_FILE:
+      return GeneralTool::kGeneralToolStamp;
+    default:
+      NOTREACHED();
+      return kToolNone;
+  }
+}
diff --git a/src/gn/tool.h b/src/gn/tool.h
new file mode 100644 (file)
index 0000000..e64935f
--- /dev/null
@@ -0,0 +1,304 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TOOL_H_
+#define TOOLS_GN_TOOL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/label.h"
+#include "gn/label_ptr.h"
+#include "gn/scope.h"
+#include "gn/source_file.h"
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+
+class ParseNode;
+class Pool;
+class Target;
+class Toolchain;
+
+class CTool;
+class GeneralTool;
+class RustTool;
+
+// To add a new Tool category, create a subclass implementing SetComplete()
+// Add a new category to ToolCategories
+// Add a GetAs* function
+class Tool {
+ public:
+  static const char* kToolNone;
+
+  virtual ~Tool();
+
+  // Manual RTTI and required functions ---------------------------------------
+  //
+  // To implement a new tool category to compile binaries in a different way,
+  // inherit this class, implement the following functions, and add the
+  // appropriate ToolTypes and RTTI getters. New tools will also need to
+  // implement a corresponding class inheriting NinjaBinaryTargetWriter that
+  // does the actual rule writing.
+
+  // Initialize tool from a scope. Child classes should override this function
+  // and call the parent.
+  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+
+  // Validate the char* passed to the creation.
+  virtual bool ValidateName(const char* name) const = 0;
+
+  // Called when the toolchain is saving this tool, after everything is filled
+  // in.
+  virtual void SetComplete() = 0;
+
+  // Validate substitutions in this tool.
+  virtual bool ValidateSubstitution(const Substitution* sub_type) const = 0;
+
+  // Manual RTTI
+  virtual CTool* AsC();
+  virtual const CTool* AsC() const;
+  virtual GeneralTool* AsGeneral();
+  virtual const GeneralTool* AsGeneral() const;
+  virtual RustTool* AsRust();
+  virtual const RustTool* AsRust() const;
+
+  // Basic information ---------------------------------------------------------
+
+  const ParseNode* defined_from() const { return defined_from_; }
+  void set_defined_from(const ParseNode* df) { defined_from_ = df; }
+
+  const char* name() const { return name_; }
+
+  // Getters/setters ----------------------------------------------------------
+  //
+  // After the tool has had its attributes set, the caller must call
+  // SetComplete(), at which point no other changes can be made.
+
+  // Command to run.
+  const SubstitutionPattern& command() const { return command_; }
+  void set_command(SubstitutionPattern cmd) {
+    DCHECK(!complete_);
+    command_ = std::move(cmd);
+  }
+
+  // Launcher for the command (e.g. goma)
+  const std::string& command_launcher() const {
+    return command_launcher_;
+  }
+  void set_command_launcher(std::string l) {
+    DCHECK(!complete_);
+    command_launcher_ = std::move(l);
+  }
+
+  // Should include a leading "." if nonempty.
+  const std::string& default_output_extension() const {
+    return default_output_extension_;
+  }
+  void set_default_output_extension(std::string ext) {
+    DCHECK(!complete_);
+    DCHECK(ext.empty() || ext[0] == '.');
+    default_output_extension_ = std::move(ext);
+  }
+
+  const SubstitutionPattern& default_output_dir() const {
+    return default_output_dir_;
+  }
+  void set_default_output_dir(SubstitutionPattern dir) {
+    DCHECK(!complete_);
+    default_output_dir_ = std::move(dir);
+  }
+
+  // Dependency file (if supported).
+  const SubstitutionPattern& depfile() const { return depfile_; }
+  void set_depfile(SubstitutionPattern df) {
+    DCHECK(!complete_);
+    depfile_ = std::move(df);
+  }
+
+  const SubstitutionPattern& description() const { return description_; }
+  void set_description(SubstitutionPattern desc) {
+    DCHECK(!complete_);
+    description_ = std::move(desc);
+  }
+
+  const std::string& framework_switch() const { return framework_switch_; }
+  void set_framework_switch(std::string s) {
+    DCHECK(!complete_);
+    framework_switch_ = std::move(s);
+  }
+
+  const std::string& weak_framework_switch() const {
+    return weak_framework_switch_;
+  }
+  void set_weak_framework_switch(std::string s) {
+    DCHECK(!complete_);
+    weak_framework_switch_ = std::move(s);
+  }
+
+  const std::string& framework_dir_switch() const {
+    return framework_dir_switch_;
+  }
+  void set_framework_dir_switch(std::string s) {
+    DCHECK(!complete_);
+    framework_dir_switch_ = std::move(s);
+  }
+
+  const std::string& lib_switch() const { return lib_switch_; }
+  void set_lib_switch(std::string s) {
+    DCHECK(!complete_);
+    lib_switch_ = std::move(s);
+  }
+
+  const std::string& lib_dir_switch() const { return lib_dir_switch_; }
+  void set_lib_dir_switch(std::string s) {
+    DCHECK(!complete_);
+    lib_dir_switch_ = std::move(s);
+  }
+
+  const std::string& swiftmodule_switch() const { return swiftmodule_switch_; }
+  void set_swiftmodule_switch(std::string s) {
+    DCHECK(!complete_);
+    swiftmodule_switch_ = std::move(s);
+  }
+
+  const std::string& linker_arg() const { return linker_arg_; }
+  void set_linker_arg(std::string s) {
+    DCHECK(!complete_);
+    linker_arg_ = std::move(s);
+  }
+
+  const SubstitutionList& outputs() const { return outputs_; }
+  void set_outputs(SubstitutionList out) {
+    DCHECK(!complete_);
+    outputs_ = std::move(out);
+  }
+
+  const SubstitutionList& partial_outputs() const { return partial_outputs_; }
+  void set_partial_outputs(SubstitutionList partial_out) {
+    DCHECK(!complete_);
+    partial_outputs_ = std::move(partial_out);
+  }
+
+  const SubstitutionList& runtime_outputs() const { return runtime_outputs_; }
+  void set_runtime_outputs(SubstitutionList run_out) {
+    DCHECK(!complete_);
+    runtime_outputs_ = std::move(run_out);
+  }
+
+  const std::string& output_prefix() const { return output_prefix_; }
+  void set_output_prefix(std::string s) {
+    DCHECK(!complete_);
+    output_prefix_ = std::move(s);
+  }
+
+  bool restat() const { return restat_; }
+  void set_restat(bool r) {
+    DCHECK(!complete_);
+    restat_ = r;
+  }
+
+  const SubstitutionPattern& rspfile() const { return rspfile_; }
+  void set_rspfile(SubstitutionPattern rsp) {
+    DCHECK(!complete_);
+    rspfile_ = std::move(rsp);
+  }
+
+  const SubstitutionPattern& rspfile_content() const {
+    return rspfile_content_;
+  }
+  void set_rspfile_content(SubstitutionPattern content) {
+    DCHECK(!complete_);
+    rspfile_content_ = std::move(content);
+  }
+
+  const LabelPtrPair<Pool>& pool() const { return pool_; }
+  void set_pool(LabelPtrPair<Pool> pool) { pool_ = std::move(pool); }
+
+  // Other functions ----------------------------------------------------------
+
+  // Function for the above override to call to complete the tool.
+  void SetToolComplete();
+
+  // Substitutions required by this tool.
+  const SubstitutionBits& substitution_bits() const {
+    DCHECK(complete_);
+    return substitution_bits_;
+  }
+
+  // Create a tool based on given features.
+  static std::unique_ptr<Tool> CreateTool(const std::string& name);
+  static std::unique_ptr<Tool> CreateTool(const ParseNode* function,
+                                          const std::string& name,
+                                          Scope* scope,
+                                          Toolchain* toolchain,
+                                          Err* err);
+
+  static const char* GetToolTypeForSourceType(SourceFile::Type type);
+  static const char* GetToolTypeForTargetFinalOutput(const Target* target);
+
+ protected:
+  explicit Tool(const char* t);
+
+  // Initialization functions -------------------------------------------------
+  //
+  // Initialization methods used by InitTool(). If successful, will set the
+  // field and return true, otherwise will return false. Must be called before
+  // SetComplete().
+  bool IsPatternInOutputList(const SubstitutionList& output_list,
+                             const SubstitutionPattern& pattern) const;
+  bool ValidateSubstitutionList(const std::vector<const Substitution*>& list,
+                                const Value* origin,
+                                Err* err) const;
+  bool ValidateOutputs(Err* err) const;
+  bool ReadBool(Scope* scope, const char* var, bool* field, Err* err);
+  bool ReadString(Scope* scope, const char* var, std::string* field, Err* err);
+  bool ReadPattern(Scope* scope,
+                   const char* var,
+                   SubstitutionPattern* field,
+                   Err* err);
+  bool ReadPatternList(Scope* scope,
+                       const char* var,
+                       SubstitutionList* field,
+                       Err* err);
+  bool ReadLabel(Scope* scope,
+                 const char* var,
+                 const Label& current_toolchain,
+                 LabelPtrPair<Pool>* field,
+                 Err* err);
+  bool ReadOutputExtension(Scope* scope, Err* err);
+
+  const ParseNode* defined_from_ = nullptr;
+  const char* name_ = nullptr;
+
+  SubstitutionPattern command_;
+  std::string command_launcher_;
+  std::string default_output_extension_;
+  SubstitutionPattern default_output_dir_;
+  SubstitutionPattern depfile_;
+  SubstitutionPattern description_;
+  std::string framework_switch_;
+  std::string weak_framework_switch_;
+  std::string framework_dir_switch_;
+  std::string lib_switch_;
+  std::string lib_dir_switch_;
+  std::string swiftmodule_switch_;
+  std::string linker_arg_;
+  SubstitutionList outputs_;
+  SubstitutionList partial_outputs_;
+  SubstitutionList runtime_outputs_;
+  std::string output_prefix_;
+  bool restat_ = false;
+  SubstitutionPattern rspfile_;
+  SubstitutionPattern rspfile_content_;
+  LabelPtrPair<Pool> pool_;
+
+  bool complete_ = false;
+
+  SubstitutionBits substitution_bits_;
+
+  DISALLOW_COPY_AND_ASSIGN(Tool);
+};
+
+#endif  // TOOLS_GN_TOOL_H_
diff --git a/src/gn/toolchain.cc b/src/gn/toolchain.cc
new file mode 100644 (file)
index 0000000..bfad81d
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/toolchain.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "gn/target.h"
+#include "gn/value.h"
+
+Toolchain::Toolchain(const Settings* settings,
+                     const Label& label,
+                     const SourceFileSet& build_dependency_files)
+    : Item(settings, label, build_dependency_files) {}
+
+Toolchain::~Toolchain() = default;
+
+Toolchain* Toolchain::AsToolchain() {
+  return this;
+}
+
+const Toolchain* Toolchain::AsToolchain() const {
+  return this;
+}
+
+Tool* Toolchain::GetTool(const char* name) {
+  DCHECK(name != Tool::kToolNone);
+  auto pair = tools_.find(name);
+  if (pair != tools_.end()) {
+    return pair->second.get();
+  }
+  return nullptr;
+}
+
+const Tool* Toolchain::GetTool(const char* name) const {
+  DCHECK(name != Tool::kToolNone);
+  auto pair = tools_.find(name);
+  if (pair != tools_.end()) {
+    return pair->second.get();
+  }
+  return nullptr;
+}
+
+GeneralTool* Toolchain::GetToolAsGeneral(const char* name) {
+  if (Tool* tool = GetTool(name)) {
+    return tool->AsGeneral();
+  }
+  return nullptr;
+}
+
+const GeneralTool* Toolchain::GetToolAsGeneral(const char* name) const {
+  if (const Tool* tool = GetTool(name)) {
+    return tool->AsGeneral();
+  }
+  return nullptr;
+}
+
+CTool* Toolchain::GetToolAsC(const char* name) {
+  if (Tool* tool = GetTool(name)) {
+    return tool->AsC();
+  }
+  return nullptr;
+}
+
+const CTool* Toolchain::GetToolAsC(const char* name) const {
+  if (const Tool* tool = GetTool(name)) {
+    return tool->AsC();
+  }
+  return nullptr;
+}
+
+RustTool* Toolchain::GetToolAsRust(const char* name) {
+  if (Tool* tool = GetTool(name)) {
+    return tool->AsRust();
+  }
+  return nullptr;
+}
+
+const RustTool* Toolchain::GetToolAsRust(const char* name) const {
+  if (const Tool* tool = GetTool(name)) {
+    return tool->AsRust();
+  }
+  return nullptr;
+}
+
+void Toolchain::SetTool(std::unique_ptr<Tool> t) {
+  DCHECK(t->name() != Tool::kToolNone);
+  DCHECK(tools_.find(t->name()) == tools_.end());
+  t->SetComplete();
+  tools_[t->name()] = std::move(t);
+}
+
+void Toolchain::ToolchainSetupComplete() {
+  // Collect required bits from all tools.
+  for (const auto& tool : tools_) {
+    substitution_bits_.MergeFrom(tool.second->substitution_bits());
+  }
+  setup_complete_ = true;
+}
+
+const Tool* Toolchain::GetToolForSourceType(SourceFile::Type type) const {
+  return GetTool(Tool::GetToolTypeForSourceType(type));
+}
+
+const CTool* Toolchain::GetToolForSourceTypeAsC(SourceFile::Type type) const {
+  return GetToolAsC(Tool::GetToolTypeForSourceType(type));
+}
+
+const GeneralTool* Toolchain::GetToolForSourceTypeAsGeneral(
+    SourceFile::Type type) const {
+  return GetToolAsGeneral(Tool::GetToolTypeForSourceType(type));
+}
+
+const RustTool* Toolchain::GetToolForSourceTypeAsRust(
+    SourceFile::Type type) const {
+  return GetToolAsRust(Tool::GetToolTypeForSourceType(type));
+}
+
+const Tool* Toolchain::GetToolForTargetFinalOutput(const Target* target) const {
+  return GetTool(Tool::GetToolTypeForTargetFinalOutput(target));
+}
+
+const CTool* Toolchain::GetToolForTargetFinalOutputAsC(
+    const Target* target) const {
+  return GetToolAsC(Tool::GetToolTypeForTargetFinalOutput(target));
+}
+
+const GeneralTool* Toolchain::GetToolForTargetFinalOutputAsGeneral(
+    const Target* target) const {
+  return GetToolAsGeneral(Tool::GetToolTypeForTargetFinalOutput(target));
+}
+
+const RustTool* Toolchain::GetToolForTargetFinalOutputAsRust(
+    const Target* target) const {
+  return GetToolAsRust(Tool::GetToolTypeForTargetFinalOutput(target));
+}
diff --git a/src/gn/toolchain.h b/src/gn/toolchain.h
new file mode 100644 (file)
index 0000000..eb5a60c
--- /dev/null
@@ -0,0 +1,129 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TOOLCHAIN_H_
+#define TOOLS_GN_TOOLCHAIN_H_
+
+#include <memory>
+#include <string_view>
+
+#include "base/logging.h"
+#include "gn/item.h"
+#include "gn/label_ptr.h"
+#include "gn/scope.h"
+#include "gn/substitution_type.h"
+#include "gn/tool.h"
+#include "gn/value.h"
+
+// Holds information on a specific toolchain. This data is filled in when we
+// encounter a toolchain definition.
+//
+// This class is an Item so it can participate in dependency management. In
+// particular, when a target uses a toolchain, it should have a dependency on
+// that toolchain's object so that we can be sure we loaded the toolchain
+// before generating the build for that target.
+//
+// Note on threadsafety: The label of the toolchain never changes so can
+// safely be accessed from any thread at any time (we do this when asking for
+// the toolchain name). But the values in the toolchain do, so these can't
+// be accessed until this Item is resolved.
+class Toolchain : public Item {
+ public:
+  // The Settings of an Item is always the context in which the Item was
+  // defined. For a toolchain this is confusing because this is NOT the
+  // settings object that applies to the things in the toolchain.
+  //
+  // To get the Settings object corresponding to objects loaded in the context
+  // of this toolchain (probably what you want instead), see
+  // Loader::GetToolchainSettings(). Many toolchain objects may be created in a
+  // given build, but only a few might be used, and the Loader is in charge of
+  // this process.
+  //
+  // We also track the set of build files that may affect this target, please
+  // refer to Scope for how this is determined.
+  Toolchain(const Settings* settings,
+            const Label& label,
+            const SourceFileSet& build_dependency_files = {});
+  ~Toolchain() override;
+
+  // Item overrides.
+  Toolchain* AsToolchain() override;
+  const Toolchain* AsToolchain() const override;
+
+  // Returns null if the tool hasn't been defined.
+  Tool* GetTool(const char* name);
+  const Tool* GetTool(const char* name) const;
+
+  // Returns null if the tool hasn't been defined or is not the correct type.
+  GeneralTool* GetToolAsGeneral(const char* name);
+  const GeneralTool* GetToolAsGeneral(const char* name) const;
+  CTool* GetToolAsC(const char* name);
+  const CTool* GetToolAsC(const char* name) const;
+  RustTool* GetToolAsRust(const char* name);
+  const RustTool* GetToolAsRust(const char* name) const;
+
+  // Set a tool. When all tools are configured, you should call
+  // ToolchainSetupComplete().
+  void SetTool(std::unique_ptr<Tool> t);
+
+  // Does final setup on the toolchain once all tools are known.
+  void ToolchainSetupComplete();
+
+  // Targets that must be resolved before compiling any targets.
+  const LabelTargetVector& deps() const { return deps_; }
+  LabelTargetVector& deps() { return deps_; }
+
+  // Specifies build argument overrides that will be set on the base scope. It
+  // will be as if these arguments were passed in on the command line. This
+  // allows a toolchain to override the OS type of the default toolchain or
+  // pass in other settings.
+  Scope::KeyValueMap& args() { return args_; }
+  const Scope::KeyValueMap& args() const { return args_; }
+
+  // Specifies whether public_configs and all_dependent_configs in this
+  // toolchain propagate to targets in other toolchains.
+  bool propagates_configs() const { return propagates_configs_; }
+  void set_propagates_configs(bool propagates_configs) {
+    propagates_configs_ = propagates_configs;
+  }
+
+  // Returns the tool for compiling the given source file type.
+  const Tool* GetToolForSourceType(SourceFile::Type type) const;
+  const CTool* GetToolForSourceTypeAsC(SourceFile::Type type) const;
+  const GeneralTool* GetToolForSourceTypeAsGeneral(SourceFile::Type type) const;
+  const RustTool* GetToolForSourceTypeAsRust(SourceFile::Type type) const;
+
+  // Returns the tool that produces the final output for the given target type.
+  // This isn't necessarily the tool you would expect. For copy target, this
+  // will return the stamp tool instead since the final output of a copy
+  // target is to stamp the set of copies done so there is one output.
+  const Tool* GetToolForTargetFinalOutput(const Target* target) const;
+  const CTool* GetToolForTargetFinalOutputAsC(const Target* target) const;
+  const GeneralTool* GetToolForTargetFinalOutputAsGeneral(
+      const Target* target) const;
+  const RustTool* GetToolForTargetFinalOutputAsRust(const Target* target) const;
+
+  const SubstitutionBits& substitution_bits() const {
+    DCHECK(setup_complete_);
+    return substitution_bits_;
+  }
+
+  const std::map<const char*, std::unique_ptr<Tool>>& tools() const {
+    return tools_;
+  }
+
+ private:
+  std::map<const char*, std::unique_ptr<Tool>> tools_;
+
+  bool setup_complete_ = false;
+
+  // Substitutions used by the tools in this toolchain.
+  SubstitutionBits substitution_bits_;
+
+  LabelTargetVector deps_;
+  Scope::KeyValueMap args_;
+  bool propagates_configs_ = false;
+};
+
+#endif  // TOOLS_GN_TOOLCHAIN_H_
diff --git a/src/gn/trace.cc b/src/gn/trace.cc
new file mode 100644 (file)
index 0000000..87c3cb6
--- /dev/null
@@ -0,0 +1,334 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/trace.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <map>
+#include <mutex>
+#include <sstream>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "gn/filesystem_utils.h"
+#include "gn/label.h"
+
+namespace {
+
+constexpr uint64_t kNanosecondsToMicroseconds = 1'000;
+
+class TraceLog {
+ public:
+  TraceLog() { events_.reserve(16384); }
+  // Trace items leaked intentionally.
+
+  void Add(TraceItem* item) {
+    std::lock_guard<std::mutex> lock(lock_);
+    events_.push_back(item);
+  }
+
+  // Returns a copy for threadsafety.
+  std::vector<TraceItem*> events() const { return events_; }
+
+ private:
+  std::mutex lock_;
+
+  std::vector<TraceItem*> events_;
+
+  DISALLOW_COPY_AND_ASSIGN(TraceLog);
+};
+
+TraceLog* trace_log = nullptr;
+
+struct Coalesced {
+  Coalesced() : name_ptr(nullptr), total_duration(0.0), count(0) {}
+
+  const std::string* name_ptr;  // Pointer to a string with the name in it.
+  double total_duration;
+  int count;
+};
+
+bool DurationGreater(const TraceItem* a, const TraceItem* b) {
+  return a->delta().raw() > b->delta().raw();
+}
+
+bool CoalescedDurationGreater(const Coalesced& a, const Coalesced& b) {
+  return a.total_duration > b.total_duration;
+}
+
+void SummarizeParses(std::vector<const TraceItem*>& loads, std::ostream& out) {
+  out << "File parse times: (time in ms, name)\n";
+
+  std::sort(loads.begin(), loads.end(), &DurationGreater);
+  for (auto* load : loads) {
+    out << base::StringPrintf(" %8.2f  ", load->delta().InMillisecondsF());
+    out << load->name() << std::endl;
+  }
+}
+
+void SummarizeCoalesced(std::vector<const TraceItem*>& items,
+                        std::ostream& out) {
+  // Group by file name.
+  std::map<std::string, Coalesced> coalesced;
+  for (auto* item : items) {
+    Coalesced& c = coalesced[item->name()];
+    c.name_ptr = &item->name();
+    c.total_duration += item->delta().InMillisecondsF();
+    c.count++;
+  }
+
+  // Sort by duration.
+  std::vector<Coalesced> sorted;
+  for (const auto& pair : coalesced)
+    sorted.push_back(pair.second);
+  std::sort(sorted.begin(), sorted.end(), &CoalescedDurationGreater);
+
+  for (const auto& cur : sorted) {
+    out << base::StringPrintf(" %8.2f  %d  ", cur.total_duration, cur.count);
+    out << *cur.name_ptr << std::endl;
+  }
+}
+
+void SummarizeFileExecs(std::vector<const TraceItem*>& execs,
+                        std::ostream& out) {
+  out << "File execute times: (total time in ms, # executions, name)\n";
+  SummarizeCoalesced(execs, out);
+}
+
+void SummarizeScriptExecs(std::vector<const TraceItem*>& execs,
+                          std::ostream& out) {
+  out << "Script execute times: (total time in ms, # executions, name)\n";
+  SummarizeCoalesced(execs, out);
+}
+
+}  // namespace
+
+TraceItem::TraceItem(Type type,
+                     const std::string& name,
+                     std::thread::id thread_id)
+    : type_(type), name_(name), thread_id_(thread_id) {}
+
+TraceItem::~TraceItem() = default;
+
+ScopedTrace::ScopedTrace(TraceItem::Type t, const std::string& name)
+    : item_(nullptr), done_(false) {
+  if (trace_log) {
+    item_ = new TraceItem(t, name, std::this_thread::get_id());
+    item_->set_begin(TicksNow());
+  }
+}
+
+ScopedTrace::ScopedTrace(TraceItem::Type t, const Label& label)
+    : item_(nullptr), done_(false) {
+  if (trace_log) {
+    item_ = new TraceItem(t, label.GetUserVisibleName(false),
+                          std::this_thread::get_id());
+    item_->set_begin(TicksNow());
+  }
+}
+
+ScopedTrace::~ScopedTrace() {
+  Done();
+}
+
+void ScopedTrace::SetToolchain(const Label& label) {
+  if (item_)
+    item_->set_toolchain(label.GetUserVisibleName(false));
+}
+
+void ScopedTrace::SetCommandLine(const base::CommandLine& cmdline) {
+  if (item_)
+    item_->set_cmdline(FilePathToUTF8(cmdline.GetArgumentsString()));
+}
+
+void ScopedTrace::Done() {
+  if (!done_) {
+    done_ = true;
+    if (trace_log) {
+      item_->set_end(TicksNow());
+      AddTrace(item_);
+    }
+  }
+}
+
+void EnableTracing() {
+  if (!trace_log)
+    trace_log = new TraceLog;
+}
+
+bool TracingEnabled() {
+  return !!trace_log;
+}
+
+void AddTrace(TraceItem* item) {
+  trace_log->Add(item);
+}
+
+std::string SummarizeTraces() {
+  if (!trace_log)
+    return std::string();
+
+  std::vector<TraceItem*> events = trace_log->events();
+
+  // Classify all events.
+  std::vector<const TraceItem*> parses;
+  std::vector<const TraceItem*> file_execs;
+  std::vector<const TraceItem*> script_execs;
+  std::vector<const TraceItem*> check_headers;
+  int headers_checked = 0;
+  for (auto* event : events) {
+    switch (event->type()) {
+      case TraceItem::TRACE_FILE_PARSE:
+        parses.push_back(event);
+        break;
+      case TraceItem::TRACE_FILE_EXECUTE:
+        file_execs.push_back(event);
+        break;
+      case TraceItem::TRACE_SCRIPT_EXECUTE:
+        script_execs.push_back(event);
+        break;
+      case TraceItem::TRACE_CHECK_HEADERS:
+        check_headers.push_back(event);
+        break;
+      case TraceItem::TRACE_CHECK_HEADER:
+        headers_checked++;
+        break;
+      case TraceItem::TRACE_IMPORT_LOAD:
+      case TraceItem::TRACE_IMPORT_BLOCK:
+      case TraceItem::TRACE_SETUP:
+      case TraceItem::TRACE_FILE_LOAD:
+      case TraceItem::TRACE_FILE_WRITE:
+      case TraceItem::TRACE_DEFINE_TARGET:
+      case TraceItem::TRACE_ON_RESOLVED:
+        break;  // Ignore these for the summary.
+    }
+  }
+
+  std::ostringstream out;
+  SummarizeParses(parses, out);
+  out << std::endl;
+  SummarizeFileExecs(file_execs, out);
+  out << std::endl;
+  SummarizeScriptExecs(script_execs, out);
+  out << std::endl;
+
+  // Generally there will only be one header check, but it's theoretically
+  // possible for more than one to run if more than one build is going in
+  // parallel. Just report the total of all of them.
+  if (!check_headers.empty()) {
+    double check_headers_time = 0;
+    for (auto* cur : check_headers)
+      check_headers_time += cur->delta().InMillisecondsF();
+
+    out << "Header check time: (total time in ms, files checked)\n";
+    out << base::StringPrintf(" %8.2f  %d\n", check_headers_time,
+                              headers_checked);
+  }
+
+  return out.str();
+}
+
+void SaveTraces(const base::FilePath& file_name) {
+  std::ostringstream out;
+
+  out << "{\"traceEvents\":[";
+
+  std::string quote_buffer;  // Allocate outside loop to prevent reallocationg.
+
+  // Write main thread metadata (assume this is being written on the main
+  // thread).
+  out << "{\"pid\":0,\"tid\":\"" << std::this_thread::get_id() << "\"";
+  out << ",\"ts\":0,\"ph\":\"M\",";
+  out << "\"name\":\"thread_name\",\"args\":{\"name\":\"Main thread\"}},";
+
+  std::vector<TraceItem*> events = trace_log->events();
+  for (size_t i = 0; i < events.size(); i++) {
+    const TraceItem& item = *events[i];
+
+    if (i != 0)
+      out << ",";
+    out << "{\"pid\":0,\"tid\":\"" << item.thread_id() << "\"";
+    out << ",\"ts\":" << item.begin() / kNanosecondsToMicroseconds;
+    out << ",\"ph\":\"X\"";  // "X" = complete event with begin & duration.
+    out << ",\"dur\":" << item.delta().InMicroseconds();
+
+    quote_buffer.resize(0);
+    base::EscapeJSONString(item.name(), true, &quote_buffer);
+    out << ",\"name\":" << quote_buffer;
+
+    out << ",\"cat\":";
+    switch (item.type()) {
+      case TraceItem::TRACE_SETUP:
+        out << "\"setup\"";
+        break;
+      case TraceItem::TRACE_FILE_LOAD:
+        out << "\"load\"";
+        break;
+      case TraceItem::TRACE_FILE_PARSE:
+        out << "\"parse\"";
+        break;
+      case TraceItem::TRACE_FILE_EXECUTE:
+        out << "\"file_exec\"";
+        break;
+      case TraceItem::TRACE_FILE_WRITE:
+        out << "\"file_write\"";
+        break;
+      case TraceItem::TRACE_IMPORT_LOAD:
+        out << "\"import_load\"";
+        break;
+      case TraceItem::TRACE_IMPORT_BLOCK:
+        out << "\"import_block\"";
+        break;
+      case TraceItem::TRACE_SCRIPT_EXECUTE:
+        out << "\"script_exec\"";
+        break;
+      case TraceItem::TRACE_DEFINE_TARGET:
+        out << "\"define\"";
+        break;
+      case TraceItem::TRACE_ON_RESOLVED:
+        out << "\"onresolved\"";
+        break;
+      case TraceItem::TRACE_CHECK_HEADER:
+        out << "\"hdr\"";
+        break;
+      case TraceItem::TRACE_CHECK_HEADERS:
+        out << "\"header_check\"";
+        break;
+    }
+
+    if (!item.toolchain().empty() || !item.cmdline().empty()) {
+      out << ",\"args\":{";
+      bool needs_comma = false;
+      if (!item.toolchain().empty()) {
+        quote_buffer.resize(0);
+        base::EscapeJSONString(item.toolchain(), true, &quote_buffer);
+        out << "\"toolchain\":" << quote_buffer;
+        needs_comma = true;
+      }
+      if (!item.cmdline().empty()) {
+        quote_buffer.resize(0);
+        base::EscapeJSONString(item.cmdline(), true, &quote_buffer);
+        if (needs_comma)
+          out << ",";
+        out << "\"cmdline\":" << quote_buffer;
+        needs_comma = true;
+      }
+      out << "}";
+    }
+    out << "}";
+  }
+
+  out << "]}";
+
+  std::string out_str = out.str();
+  base::WriteFile(file_name, out_str.data(), static_cast<int>(out_str.size()));
+}
diff --git a/src/gn/trace.h b/src/gn/trace.h
new file mode 100644 (file)
index 0000000..f4f06df
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TRACE_H_
+#define TOOLS_GN_TRACE_H_
+
+#include <string>
+#include <thread>
+
+#include "base/macros.h"
+#include "util/ticks.h"
+
+class Label;
+
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
+class TraceItem {
+ public:
+  enum Type {
+    TRACE_SETUP,
+    TRACE_FILE_LOAD,
+    TRACE_FILE_PARSE,
+    TRACE_FILE_EXECUTE,
+    TRACE_FILE_WRITE,
+    TRACE_IMPORT_LOAD,
+    TRACE_IMPORT_BLOCK,
+    TRACE_SCRIPT_EXECUTE,
+    TRACE_DEFINE_TARGET,
+    TRACE_ON_RESOLVED,
+    TRACE_CHECK_HEADER,   // One file.
+    TRACE_CHECK_HEADERS,  // All files.
+  };
+
+  TraceItem(Type type, const std::string& name, std::thread::id thread_id);
+  ~TraceItem();
+
+  Type type() const { return type_; }
+  const std::string& name() const { return name_; }
+  std::thread::id thread_id() const { return thread_id_; }
+
+  Ticks begin() const { return begin_; }
+  void set_begin(Ticks b) { begin_ = b; }
+  Ticks end() const { return end_; }
+  void set_end(Ticks e) { end_ = e; }
+
+  TickDelta delta() const { return TicksDelta(end_, begin_); }
+
+  // Optional toolchain label.
+  const std::string& toolchain() const { return toolchain_; }
+  void set_toolchain(const std::string& t) { toolchain_ = t; }
+
+  // Optional command line.
+  const std::string& cmdline() const { return cmdline_; }
+  void set_cmdline(const std::string& c) { cmdline_ = c; }
+
+ private:
+  Type type_;
+  std::string name_;
+  std::thread::id thread_id_;
+
+  Ticks begin_;
+  Ticks end_;
+
+  std::string toolchain_;
+  std::string cmdline_;
+};
+
+class ScopedTrace {
+ public:
+  ScopedTrace(TraceItem::Type t, const std::string& name);
+  ScopedTrace(TraceItem::Type t, const Label& label);
+  ~ScopedTrace();
+
+  void SetToolchain(const Label& label);
+  void SetCommandLine(const base::CommandLine& cmdline);
+
+  void Done();
+
+ private:
+  TraceItem* item_;
+  bool done_;
+};
+
+// Call to turn tracing on. It's off by default.
+void EnableTracing();
+
+// Returns whether tracing is enabled.
+bool TracingEnabled();
+
+// Adds a trace event to the log. Takes ownership of the pointer.
+void AddTrace(TraceItem* item);
+
+// Returns a summary of the current traces, or the empty string if tracing is
+// not enabled.
+std::string SummarizeTraces();
+
+// Saves the current traces to the given filename in JSON format.
+void SaveTraces(const base::FilePath& file_name);
+
+#endif  // TOOLS_GN_TRACE_H_
diff --git a/src/gn/unique_vector.h b/src/gn/unique_vector.h
new file mode 100644 (file)
index 0000000..ce5380b
--- /dev/null
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_UNIQUE_VECTOR_H_
+#define TOOLS_GN_UNIQUE_VECTOR_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <unordered_set>
+#include <vector>
+
+#include "hash_table_base.h"
+
+// A HashTableBase node type used by all UniqueVector<> instantiations.
+// The node stores the item's hash value and its index plus 1, in order
+// to follow the HashTableBase requirements (i.e. zero-initializable null
+// value).
+struct UniqueVectorNode {
+  size_t hash;
+  size_t index_plus1;
+
+  size_t hash_value() const { return hash; }
+
+  bool is_valid() const { return !is_null(); }
+
+  bool is_null() const { return index_plus1 == 0; }
+
+  // Do not support deletion, making lookup faster.
+  static constexpr bool is_tombstone() { return false; }
+
+  // Return vector index.
+  size_t index() const { return index_plus1 - 1u; }
+
+  // Create new instance from hash value and vector index.
+  static UniqueVectorNode Make(size_t hash, size_t index) {
+    return {hash, index + 1u};
+  }
+};
+
+using UniqueVectorHashTableBase = HashTableBase<UniqueVectorNode>;
+
+// A common HashSet implementation used by all UniqueVector instantiations.
+class UniqueVectorHashSet : public UniqueVectorHashTableBase {
+ public:
+  using BaseType = UniqueVectorHashTableBase;
+  using Node = BaseType::Node;
+
+  // Specialized Lookup() template function.
+  // |hash| is the hash value for |item|.
+  // |item| is the item search key being looked up.
+  // |vector| is containing vector for existing items.
+  //
+  // Returns a non-null mutable Node pointer.
+  template <typename T>
+  Node* Lookup(size_t hash, const T& item, const std::vector<T>& vector) const {
+    return BaseType::NodeLookup(hash, [&](const Node* node) {
+      return node->hash == hash && vector[node->index()] == item;
+    });
+  }
+
+  // Specialized Insert() function that converts |index| into the proper
+  // UniqueVectorKey type.
+  void Insert(Node* node, size_t hash, size_t index) {
+    *node = Node::Make(hash, index);
+    BaseType::UpdateAfterInsert();
+  }
+
+  void Clear() { NodeClear(); }
+};
+
+// An ordered set optimized for GN's usage. Such sets are used to store lists
+// of configs and libraries, and are appended to but not randomly inserted
+// into.
+template <typename T>
+class UniqueVector {
+ public:
+  using Vector = std::vector<T>;
+  using iterator = typename Vector::iterator;
+  using const_iterator = typename Vector::const_iterator;
+
+  const Vector& vector() const { return vector_; }
+  size_t size() const { return vector_.size(); }
+  bool empty() const { return vector_.empty(); }
+  void clear() {
+    vector_.clear();
+    set_.Clear();
+  }
+  void reserve(size_t s) { vector_.reserve(s); }
+
+  const T& operator[](size_t index) const { return vector_[index]; }
+
+  const_iterator begin() const { return vector_.begin(); }
+  const_iterator end() const { return vector_.end(); }
+
+  // Returns true if the item was appended, false if it already existed (and
+  // thus the vector was not modified).
+  bool push_back(const T& t) {
+    size_t hash = std::hash<T>()(t);
+    auto* node = set_.Lookup(hash, t, vector_);
+    if (node->is_valid()) {
+      return false;  // Already have this one.
+    }
+
+    vector_.push_back(t);
+    set_.Insert(node, hash, vector_.size() - 1);
+    return true;
+  }
+
+  bool push_back(T&& t) {
+    size_t hash = std::hash<T>()(t);
+    auto* node = set_.Lookup(hash, t, vector_);
+    if (node->is_valid()) {
+      return false;  // Already have this one.
+    }
+
+    vector_.push_back(std::move(t));
+    set_.Insert(node, hash, vector_.size() - 1);
+    return true;
+  }
+
+  // Appends a range of items from an iterator.
+  template <typename iter>
+  void Append(const iter& begin, const iter& end) {
+    for (iter i = begin; i != end; ++i)
+      push_back(*i);
+  }
+
+  // Returns the index of the item matching the given value in the list, or
+  // (size_t)(-1) if it's not found.
+  size_t IndexOf(const T& t) const {
+    size_t hash = std::hash<T>()(t);
+    auto* node = set_.Lookup(hash, t, vector_);
+    return node->index();
+  }
+
+ private:
+  Vector vector_;
+  UniqueVectorHashSet set_;
+};
+
+#endif  // TOOLS_GN_UNIQUE_VECTOR_H_
diff --git a/src/gn/unique_vector_unittest.cc b/src/gn/unique_vector_unittest.cc
new file mode 100644 (file)
index 0000000..96303b4
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/unique_vector.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "util/test/test.h"
+
+TEST(UniqueVector, PushBack) {
+  UniqueVector<int> foo;
+  EXPECT_TRUE(foo.push_back(1));
+  EXPECT_FALSE(foo.push_back(1));
+  EXPECT_TRUE(foo.push_back(2));
+  EXPECT_TRUE(foo.push_back(0));
+  EXPECT_FALSE(foo.push_back(2));
+  EXPECT_FALSE(foo.push_back(1));
+
+  EXPECT_EQ(3u, foo.size());
+  EXPECT_EQ(1, foo[0]);
+  EXPECT_EQ(2, foo[1]);
+  EXPECT_EQ(0, foo[2]);
+
+  // Verify those results with IndexOf as well.
+  EXPECT_EQ(0u, foo.IndexOf(1));
+  EXPECT_EQ(1u, foo.IndexOf(2));
+  EXPECT_EQ(2u, foo.IndexOf(0));
+  EXPECT_EQ(static_cast<size_t>(-1), foo.IndexOf(99));
+}
+
+TEST(UniqueVector, PushBackMove) {
+  UniqueVector<std::string> vect;
+  std::string a("a");
+  EXPECT_TRUE(vect.push_back(std::move(a)));
+  EXPECT_EQ("", a);
+
+  a = "a";
+  EXPECT_FALSE(vect.push_back(std::move(a)));
+  EXPECT_EQ("a", a);
+
+  EXPECT_EQ(0u, vect.IndexOf("a"));
+}
diff --git a/src/gn/value.cc b/src/gn/value.cc
new file mode 100644 (file)
index 0000000..c3bb9b5
--- /dev/null
@@ -0,0 +1,262 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/value.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "gn/scope.h"
+
+// NOTE: Cannot use = default here due to the use of a union member.
+Value::Value() {}
+
+Value::Value(const ParseNode* origin, Type t) : type_(t), origin_(origin) {
+  switch (type_) {
+    case NONE:
+      break;
+    case BOOLEAN:
+      boolean_value_ = false;
+      break;
+    case INTEGER:
+      int_value_ = 0;
+      break;
+    case STRING:
+      new (&string_value_) std::string();
+      break;
+    case LIST:
+      new (&list_value_) std::vector<Value>();
+      break;
+    case SCOPE:
+      new (&scope_value_) std::unique_ptr<Scope>();
+      break;
+  }
+}
+
+Value::Value(const ParseNode* origin, bool bool_val)
+    : type_(BOOLEAN), origin_(origin), boolean_value_(bool_val) {}
+
+Value::Value(const ParseNode* origin, int64_t int_val)
+    : type_(INTEGER), origin_(origin), int_value_(int_val) {}
+
+Value::Value(const ParseNode* origin, std::string str_val)
+    : type_(STRING), origin_(origin), string_value_(std::move(str_val)) {}
+
+Value::Value(const ParseNode* origin, const char* str_val)
+    : type_(STRING), origin_(origin), string_value_(str_val) {}
+
+Value::Value(const ParseNode* origin, std::unique_ptr<Scope> scope)
+    : type_(SCOPE), origin_(origin), scope_value_(std::move(scope)) {}
+
+Value::Value(const Value& other) : type_(other.type_), origin_(other.origin_) {
+  switch (type_) {
+    case NONE:
+      break;
+    case BOOLEAN:
+      boolean_value_ = other.boolean_value_;
+      break;
+    case INTEGER:
+      int_value_ = other.int_value_;
+      break;
+    case STRING:
+      new (&string_value_) std::string(other.string_value_);
+      break;
+    case LIST:
+      new (&list_value_) std::vector<Value>(other.list_value_);
+      break;
+    case SCOPE:
+      new (&scope_value_) std::unique_ptr<Scope>(
+          other.scope_value_.get() ? other.scope_value_->MakeClosure()
+                                   : nullptr);
+      break;
+  }
+}
+
+Value::Value(Value&& other) noexcept
+    : type_(other.type_), origin_(other.origin_) {
+  switch (type_) {
+    case NONE:
+      break;
+    case BOOLEAN:
+      boolean_value_ = other.boolean_value_;
+      break;
+    case INTEGER:
+      int_value_ = other.int_value_;
+      break;
+    case STRING:
+      new (&string_value_) std::string(std::move(other.string_value_));
+      break;
+    case LIST:
+      new (&list_value_) std::vector<Value>(std::move(other.list_value_));
+      break;
+    case SCOPE:
+      new (&scope_value_) std::unique_ptr<Scope>(std::move(other.scope_value_));
+      break;
+  }
+}
+
+Value& Value::operator=(const Value& other) {
+  if (this != &other) {
+    this->~Value();
+    new (this) Value(other);
+  }
+  return *this;
+}
+
+Value& Value::operator=(Value&& other) noexcept {
+  if (this != &other) {
+    this->~Value();
+    new (this) Value(std::move(other));
+  }
+  return *this;
+}
+
+Value::~Value() {
+  using namespace std;
+  switch (type_) {
+    case STRING:
+      string_value_.~string();
+      break;
+    case LIST:
+      list_value_.~vector<Value>();
+      break;
+    case SCOPE:
+      scope_value_.~unique_ptr<Scope>();
+      break;
+    default:;
+  }
+}
+
+// static
+const char* Value::DescribeType(Type t) {
+  switch (t) {
+    case NONE:
+      return "none";
+    case BOOLEAN:
+      return "boolean";
+    case INTEGER:
+      return "integer";
+    case STRING:
+      return "string";
+    case LIST:
+      return "list";
+    case SCOPE:
+      return "scope";
+    default:
+      NOTREACHED();
+      return "UNKNOWN";
+  }
+}
+
+void Value::SetScopeValue(std::unique_ptr<Scope> scope) {
+  DCHECK(type_ == SCOPE);
+  scope_value_ = std::move(scope);
+}
+
+std::string Value::ToString(bool quote_string) const {
+  switch (type_) {
+    case NONE:
+      return "<void>";
+    case BOOLEAN:
+      return boolean_value_ ? "true" : "false";
+    case INTEGER:
+      return base::Int64ToString(int_value_);
+    case STRING:
+      if (quote_string) {
+        std::string result = "\"";
+        bool hanging_backslash = false;
+        for (char ch : string_value_) {
+          // If the last character was a literal backslash and the next
+          // character could form a valid escape sequence, we need to insert
+          // an extra backslash to prevent that.
+          if (hanging_backslash && (ch == '$' || ch == '"' || ch == '\\'))
+            result += '\\';
+          // If the next character is a dollar sign or double quote, it needs
+          // to be escaped; otherwise it can be printed as is.
+          if (ch == '$' || ch == '"')
+            result += '\\';
+          result += ch;
+          hanging_backslash = (ch == '\\');
+        }
+        // Again, we need to prevent the closing double quotes from becoming
+        // an escape sequence.
+        if (hanging_backslash)
+          result += '\\';
+        result += '"';
+        return result;
+      }
+      return string_value_;
+    case LIST: {
+      std::string result = "[";
+      for (size_t i = 0; i < list_value_.size(); i++) {
+        if (i > 0)
+          result += ", ";
+        result += list_value_[i].ToString(true);
+      }
+      result.push_back(']');
+      return result;
+    }
+    case SCOPE: {
+      Scope::KeyValueMap scope_values;
+      scope_value_->GetCurrentScopeValues(&scope_values);
+      if (scope_values.empty())
+        return std::string("{ }");
+
+      std::string result = "{\n";
+      for (const auto& pair : scope_values) {
+        result += "  " + std::string(pair.first) + " = " +
+                  pair.second.ToString(true) + "\n";
+      }
+      result += "}";
+
+      return result;
+    }
+  }
+  return std::string();
+}
+
+bool Value::VerifyTypeIs(Type t, Err* err) const {
+  if (type_ == t)
+    return true;
+
+  *err = Err(origin(), std::string("This is not a ") + DescribeType(t) + ".",
+             std::string("Instead I see a ") + DescribeType(type_) + " = " +
+                 ToString(true));
+  return false;
+}
+
+bool Value::operator==(const Value& other) const {
+  if (type_ != other.type_)
+    return false;
+
+  switch (type_) {
+    case Value::BOOLEAN:
+      return boolean_value() == other.boolean_value();
+    case Value::INTEGER:
+      return int_value() == other.int_value();
+    case Value::STRING:
+      return string_value() == other.string_value();
+    case Value::LIST:
+      if (list_value().size() != other.list_value().size())
+        return false;
+      for (size_t i = 0; i < list_value().size(); i++) {
+        if (list_value()[i] != other.list_value()[i])
+          return false;
+      }
+      return true;
+    case Value::SCOPE:
+      return scope_value()->CheckCurrentScopeValuesEqual(other.scope_value());
+    case Value::NONE:
+      return false;
+    default:
+      NOTREACHED();
+      return false;
+  }
+}
+
+bool Value::operator!=(const Value& other) const {
+  return !operator==(other);
+}
diff --git a/src/gn/value.h b/src/gn/value.h
new file mode 100644 (file)
index 0000000..bf01073
--- /dev/null
@@ -0,0 +1,137 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VALUE_H_
+#define TOOLS_GN_VALUE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "gn/err.h"
+
+class ParseNode;
+class Scope;
+
+// Represents a variable value in the interpreter.
+class Value {
+ public:
+  enum Type {
+    NONE = 0,
+    BOOLEAN,
+    INTEGER,
+    STRING,
+    LIST,
+    SCOPE,
+  };
+
+  Value();
+  Value(const ParseNode* origin, Type t);
+  Value(const ParseNode* origin, bool bool_val);
+  Value(const ParseNode* origin, int64_t int_val);
+  Value(const ParseNode* origin, std::string str_val);
+  Value(const ParseNode* origin, const char* str_val);
+  // Values "shouldn't" have null scopes when type == Scope, so be sure to
+  // always set one. However, this is not asserted since there are some
+  // use-cases for creating values and immediately setting the scope on it. So
+  // you can pass a null scope here if you promise to set it before any other
+  // code gets it (code will generally assume the scope is not null).
+  Value(const ParseNode* origin, std::unique_ptr<Scope> scope);
+
+  Value(const Value& other);
+  Value(Value&& other) noexcept;
+  ~Value();
+
+  Value& operator=(const Value& other);
+  Value& operator=(Value&& other) noexcept;
+
+  Type type() const { return type_; }
+
+  // Returns a string describing the given type.
+  static const char* DescribeType(Type t);
+
+  // Returns the node that made this. May be NULL.
+  const ParseNode* origin() const { return origin_; }
+  void set_origin(const ParseNode* o) { origin_ = o; }
+
+  bool& boolean_value() {
+    DCHECK(type_ == BOOLEAN);
+    return boolean_value_;
+  }
+  const bool& boolean_value() const {
+    DCHECK(type_ == BOOLEAN);
+    return boolean_value_;
+  }
+
+  int64_t& int_value() {
+    DCHECK(type_ == INTEGER);
+    return int_value_;
+  }
+  const int64_t& int_value() const {
+    DCHECK(type_ == INTEGER);
+    return int_value_;
+  }
+
+  std::string& string_value() {
+    DCHECK(type_ == STRING);
+    return string_value_;
+  }
+  const std::string& string_value() const {
+    DCHECK(type_ == STRING);
+    return string_value_;
+  }
+
+  std::vector<Value>& list_value() {
+    DCHECK(type_ == LIST);
+    return list_value_;
+  }
+  const std::vector<Value>& list_value() const {
+    DCHECK(type_ == LIST);
+    return list_value_;
+  }
+
+  Scope* scope_value() {
+    DCHECK(type_ == SCOPE);
+    return scope_value_.get();
+  }
+  const Scope* scope_value() const {
+    DCHECK(type_ == SCOPE);
+    return scope_value_.get();
+  }
+  void SetScopeValue(std::unique_ptr<Scope> scope);
+
+  // Converts the given value to a string. Returns true if strings should be
+  // quoted or the ToString of a string should be the string itself. If the
+  // string is quoted, it will also enable escaping.
+  std::string ToString(bool quote_strings) const;
+
+  // Verifies that the value is of the given type. If it isn't, returns
+  // false and sets the error.
+  bool VerifyTypeIs(Type t, Err* err) const;
+
+  // Compares values. Only the "value" is compared, not the origin. Scope
+  // values check only the contents of the current scope, and do not go to
+  // parent scopes.
+  bool operator==(const Value& other) const;
+  bool operator!=(const Value& other) const;
+
+ private:
+  void Deallocate();
+
+  Type type_ = NONE;
+  const ParseNode* origin_ = nullptr;
+
+  union {
+    bool boolean_value_;
+    int64_t int_value_;
+    std::string string_value_;
+    std::vector<Value> list_value_;
+    std::unique_ptr<Scope> scope_value_;
+  };
+};
+
+#endif  // TOOLS_GN_VALUE_H_
diff --git a/src/gn/value_extractors.cc b/src/gn/value_extractors.cc
new file mode 100644 (file)
index 0000000..9eb7593
--- /dev/null
@@ -0,0 +1,323 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/value_extractors.h"
+
+#include <stddef.h>
+
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/frameworks_utils.h"
+#include "gn/label.h"
+#include "gn/source_dir.h"
+#include "gn/source_file.h"
+#include "gn/target.h"
+#include "gn/value.h"
+
+namespace {
+
+// Sets the error and returns false on failure.
+template <typename T, class Converter>
+bool ListValueExtractor(const Value& value,
+                        std::vector<T>* dest,
+                        Err* err,
+                        const Converter& converter) {
+  if (!value.VerifyTypeIs(Value::LIST, err))
+    return false;
+  const std::vector<Value>& input_list = value.list_value();
+  dest->resize(input_list.size());
+  for (size_t i = 0; i < input_list.size(); i++) {
+    if (!converter(input_list[i], &(*dest)[i], err))
+      return false;
+  }
+  return true;
+}
+
+// Like the above version but extracts to a UniqueVector and sets the error if
+// there are duplicates.
+template <typename T, class Converter>
+bool ListValueUniqueExtractor(const Value& value,
+                              UniqueVector<T>* dest,
+                              Err* err,
+                              const Converter& converter) {
+  if (!value.VerifyTypeIs(Value::LIST, err))
+    return false;
+  const std::vector<Value>& input_list = value.list_value();
+
+  for (const auto& item : input_list) {
+    T new_one;
+    if (!converter(item, &new_one, err))
+      return false;
+    if (!dest->push_back(new_one)) {
+      // Already in the list, throw error.
+      *err = Err(item, "Duplicate item in list");
+      size_t previous_index = dest->IndexOf(new_one);
+      err->AppendSubErr(
+          Err(input_list[previous_index], "This was the previous definition."));
+      return false;
+    }
+  }
+  return true;
+}
+
+struct RelativeFileConverter {
+  RelativeFileConverter(const BuildSettings* build_settings_in,
+                        const SourceDir& current_dir_in)
+      : build_settings(build_settings_in), current_dir(current_dir_in) {}
+  bool operator()(const Value& v, SourceFile* out, Err* err) const {
+    *out = current_dir.ResolveRelativeFile(v, err,
+                                           build_settings->root_path_utf8());
+    return !err->has_error();
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+};
+
+struct LibFileConverter {
+  LibFileConverter(const BuildSettings* build_settings_in,
+                   const SourceDir& current_dir_in)
+      : build_settings(build_settings_in), current_dir(current_dir_in) {}
+  bool operator()(const Value& v, LibFile* out, Err* err) const {
+    if (!v.VerifyTypeIs(Value::STRING, err))
+      return false;
+    if (!GetFrameworkName(v.string_value()).empty()) {
+      *err = Err(v, "Unsupported value in libs.",
+                 "Use frameworks to list framework dependencies.");
+      return false;
+    }
+    if (v.string_value().find('/') == std::string::npos) {
+      *out = LibFile(v.string_value());
+    } else {
+      *out = LibFile(current_dir.ResolveRelativeFile(
+          v, err, build_settings->root_path_utf8()));
+    }
+    return !err->has_error();
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+};
+
+struct RelativeDirConverter {
+  RelativeDirConverter(const BuildSettings* build_settings_in,
+                       const SourceDir& current_dir_in)
+      : build_settings(build_settings_in), current_dir(current_dir_in) {}
+  bool operator()(const Value& v, SourceDir* out, Err* err) const {
+    *out = current_dir.ResolveRelativeDir(v, err,
+                                          build_settings->root_path_utf8());
+    return true;
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+};
+
+struct ExternConverter {
+  ExternConverter(const BuildSettings* build_settings_in,
+                  const SourceDir& current_dir_in)
+      : build_settings(build_settings_in), current_dir(current_dir_in) {}
+  bool operator()(const Value& v,
+                  std::pair<std::string, LibFile>* out,
+                  Err* err) const {
+    if (!v.VerifyTypeIs(Value::SCOPE, err))
+      return false;
+    Scope::KeyValueMap scope;
+    v.scope_value()->GetCurrentScopeValues(&scope);
+    std::string cratename;
+    if (auto it = scope.find("crate_name"); it != scope.end()) {
+      if (!it->second.VerifyTypeIs(Value::STRING, err))
+        return false;
+      cratename = it->second.string_value();
+    } else {
+      return false;
+    }
+    LibFile path;
+    if (auto it = scope.find("path"); it != scope.end()) {
+      if (!it->second.VerifyTypeIs(Value::STRING, err))
+        return false;
+      if (it->second.string_value().find('/') == std::string::npos) {
+        path = LibFile(it->second.string_value());
+      } else {
+        path = LibFile(current_dir.ResolveRelativeFile(
+            it->second, err, build_settings->root_path_utf8()));
+      }
+    } else {
+      return false;
+    }
+    *out = std::pair(cratename, path);
+    return !err->has_error();
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+};
+
+// Fills in a label.
+template <typename T>
+struct LabelResolver {
+  LabelResolver(const BuildSettings* build_settings_in,
+                const SourceDir& current_dir_in,
+                const Label& current_toolchain_in)
+      : build_settings(build_settings_in),
+        current_dir(current_dir_in),
+        current_toolchain(current_toolchain_in) {}
+  bool operator()(const Value& v, Label* out, Err* err) const {
+    if (!v.VerifyTypeIs(Value::STRING, err))
+      return false;
+    *out = Label::Resolve(current_dir, build_settings->root_path_utf8(),
+                          current_toolchain, v, err);
+    return !err->has_error();
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+  const Label& current_toolchain;
+};
+
+// Fills the label part of a LabelPtrPair, leaving the pointer null.
+template <typename T>
+struct LabelPtrResolver {
+  LabelPtrResolver(const BuildSettings* build_settings_in,
+                   const SourceDir& current_dir_in,
+                   const Label& current_toolchain_in)
+      : build_settings(build_settings_in),
+        current_dir(current_dir_in),
+        current_toolchain(current_toolchain_in) {}
+  bool operator()(const Value& v, LabelPtrPair<T>* out, Err* err) const {
+    if (!v.VerifyTypeIs(Value::STRING, err))
+      return false;
+    out->label = Label::Resolve(current_dir, build_settings->root_path_utf8(),
+                                current_toolchain, v, err);
+    out->origin = v.origin();
+    return !err->has_error();
+  }
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+  const Label& current_toolchain;
+};
+
+struct LabelPatternResolver {
+  LabelPatternResolver(const BuildSettings* build_settings_in,
+                       const SourceDir& current_dir_in)
+      : build_settings(build_settings_in), current_dir(current_dir_in) {}
+  bool operator()(const Value& v, LabelPattern* out, Err* err) const {
+    *out = LabelPattern::GetPattern(current_dir,
+                                    build_settings->root_path_utf8(), v, err);
+    return !err->has_error();
+  }
+
+  const BuildSettings* build_settings;
+  const SourceDir& current_dir;
+};
+
+}  // namespace
+
+bool ExtractListOfStringValues(const Value& value,
+                               std::vector<std::string>* dest,
+                               Err* err) {
+  if (!value.VerifyTypeIs(Value::LIST, err))
+    return false;
+  const std::vector<Value>& input_list = value.list_value();
+  dest->reserve(input_list.size());
+  for (const auto& item : input_list) {
+    if (!item.VerifyTypeIs(Value::STRING, err))
+      return false;
+    dest->push_back(item.string_value());
+  }
+  return true;
+}
+
+bool ExtractListOfRelativeFiles(const BuildSettings* build_settings,
+                                const Value& value,
+                                const SourceDir& current_dir,
+                                std::vector<SourceFile>* files,
+                                Err* err) {
+  return ListValueExtractor(value, files, err,
+                            RelativeFileConverter(build_settings, current_dir));
+}
+
+bool ExtractListOfLibs(const BuildSettings* build_settings,
+                       const Value& value,
+                       const SourceDir& current_dir,
+                       std::vector<LibFile>* libs,
+                       Err* err) {
+  return ListValueExtractor(value, libs, err,
+                            LibFileConverter(build_settings, current_dir));
+}
+
+bool ExtractListOfRelativeDirs(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               std::vector<SourceDir>* dest,
+                               Err* err) {
+  return ListValueExtractor(value, dest, err,
+                            RelativeDirConverter(build_settings, current_dir));
+}
+
+bool ExtractListOfLabels(const BuildSettings* build_settings,
+                         const Value& value,
+                         const SourceDir& current_dir,
+                         const Label& current_toolchain,
+                         LabelTargetVector* dest,
+                         Err* err) {
+  return ListValueExtractor(
+      value, dest, err,
+      LabelPtrResolver<Target>(build_settings, current_dir, current_toolchain));
+}
+
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<Label>* dest,
+                               Err* err) {
+  return ListValueUniqueExtractor(
+      value, dest, err,
+      LabelResolver<Config>(build_settings, current_dir, current_toolchain));
+}
+
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<LabelConfigPair>* dest,
+                               Err* err) {
+  return ListValueUniqueExtractor(
+      value, dest, err,
+      LabelPtrResolver<Config>(build_settings, current_dir, current_toolchain));
+}
+
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<LabelTargetPair>* dest,
+                               Err* err) {
+  return ListValueUniqueExtractor(
+      value, dest, err,
+      LabelPtrResolver<Target>(build_settings, current_dir, current_toolchain));
+}
+
+bool ExtractRelativeFile(const BuildSettings* build_settings,
+                         const Value& value,
+                         const SourceDir& current_dir,
+                         SourceFile* file,
+                         Err* err) {
+  RelativeFileConverter converter(build_settings, current_dir);
+  return converter(value, file, err);
+}
+
+bool ExtractListOfLabelPatterns(const BuildSettings* build_settings,
+                                const Value& value,
+                                const SourceDir& current_dir,
+                                std::vector<LabelPattern>* patterns,
+                                Err* err) {
+  return ListValueExtractor(value, patterns, err,
+                            LabelPatternResolver(build_settings, current_dir));
+}
+
+bool ExtractListOfExterns(const BuildSettings* build_settings,
+                          const Value& value,
+                          const SourceDir& current_dir,
+                          std::vector<std::pair<std::string, LibFile>>* externs,
+                          Err* err) {
+  return ListValueExtractor(value, externs, err,
+                            ExternConverter(build_settings, current_dir));
+}
diff --git a/src/gn/value_extractors.h b/src/gn/value_extractors.h
new file mode 100644 (file)
index 0000000..9cc9e00
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VALUE_EXTRACTORS_H_
+#define TOOLS_GN_VALUE_EXTRACTORS_H_
+
+#include <string>
+#include <vector>
+
+#include "gn/label_ptr.h"
+#include "gn/lib_file.h"
+#include "gn/unique_vector.h"
+
+class BuildSettings;
+class Err;
+class Label;
+class LabelPattern;
+class SourceDir;
+class SourceFile;
+class Value;
+
+// On failure, returns false and sets the error.
+bool ExtractListOfStringValues(const Value& value,
+                               std::vector<std::string>* dest,
+                               Err* err);
+
+// Looks for a list of source files relative to a given current dir.
+bool ExtractListOfRelativeFiles(const BuildSettings* build_settings,
+                                const Value& value,
+                                const SourceDir& current_dir,
+                                std::vector<SourceFile>* files,
+                                Err* err);
+
+// Extracts a list of libraries. When they contain a "/" they are treated as
+// source paths and are otherwise treated as plain strings.
+bool ExtractListOfLibs(const BuildSettings* build_settings,
+                       const Value& value,
+                       const SourceDir& current_dir,
+                       std::vector<LibFile>* libs,
+                       Err* err);
+
+// Looks for a list of source directories relative to a given current dir.
+bool ExtractListOfRelativeDirs(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               std::vector<SourceDir>* dest,
+                               Err* err);
+
+// Extracts the list of labels and their origins to the given vector. Only the
+// labels are filled in, the ptr for each pair in the vector will be null.
+bool ExtractListOfLabels(const BuildSettings* build_settings,
+                         const Value& value,
+                         const SourceDir& current_dir,
+                         const Label& current_toolchain,
+                         LabelTargetVector* dest,
+                         Err* err);
+
+// Extracts the list of labels and their origins to the given vector. For the
+// version taking Label*Pair, only the labels are filled in, the ptr for each
+// pair in the vector will be null. Sets an error and returns false if a label
+// is maformed or there are duplicates.
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<Label>* dest,
+                               Err* err);
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<LabelConfigPair>* dest,
+                               Err* err);
+bool ExtractListOfUniqueLabels(const BuildSettings* build_settings,
+                               const Value& value,
+                               const SourceDir& current_dir,
+                               const Label& current_toolchain,
+                               UniqueVector<LabelTargetPair>* dest,
+                               Err* err);
+
+bool ExtractRelativeFile(const BuildSettings* build_settings,
+                         const Value& value,
+                         const SourceDir& current_dir,
+                         SourceFile* file,
+                         Err* err);
+
+bool ExtractListOfLabelPatterns(const BuildSettings* build_settings,
+                                const Value& value,
+                                const SourceDir& current_dir,
+                                std::vector<LabelPattern>* patterns,
+                                Err* err);
+
+bool ExtractListOfExterns(const BuildSettings* build_settings,
+                          const Value& value,
+                          const SourceDir& current_dir,
+                          std::vector<std::pair<std::string, LibFile>>* externs,
+                          Err* err);
+
+#endif  // TOOLS_GN_VALUE_EXTRACTORS_H_
diff --git a/src/gn/value_unittest.cc b/src/gn/value_unittest.cc
new file mode 100644 (file)
index 0000000..555f101
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "gn/test_with_scope.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+TEST(Value, ToString) {
+  Value strval(nullptr, "hi\" $me\\you\\$\\\"");
+  EXPECT_EQ("hi\" $me\\you\\$\\\"", strval.ToString(false));
+  EXPECT_EQ("\"hi\\\" \\$me\\you\\\\\\$\\\\\\\"\"", strval.ToString(true));
+
+  // crbug.com/470217
+  Value strval2(nullptr, "\\foo\\\\bar\\");
+  EXPECT_EQ("\"\\foo\\\\\\bar\\\\\"", strval2.ToString(true));
+
+  // Void type.
+  EXPECT_EQ("<void>", Value().ToString(false));
+
+  // Test lists, bools, and ints.
+  Value listval(nullptr, Value::LIST);
+  listval.list_value().push_back(Value(nullptr, "hi\"me"));
+  listval.list_value().push_back(Value(nullptr, true));
+  listval.list_value().push_back(Value(nullptr, false));
+  listval.list_value().push_back(Value(nullptr, static_cast<int64_t>(42)));
+  // Printing lists always causes embedded strings to be quoted (ignoring the
+  // quote flag), or else they wouldn't make much sense.
+  EXPECT_EQ("[\"hi\\\"me\", true, false, 42]", listval.ToString(false));
+  EXPECT_EQ("[\"hi\\\"me\", true, false, 42]", listval.ToString(true));
+
+  // Scopes.
+  TestWithScope setup;
+  Scope* scope = new Scope(setup.settings());
+  Value scopeval(nullptr, std::unique_ptr<Scope>(scope));
+  EXPECT_EQ("{ }", scopeval.ToString(false));
+
+  // Test that an empty scope equals an empty scope.
+  EXPECT_TRUE(scopeval == scopeval);
+
+  scope->SetValue("a", Value(nullptr, static_cast<int64_t>(42)), nullptr);
+  scope->SetValue("b", Value(nullptr, "hello, world"), nullptr);
+  EXPECT_EQ("{\n  a = 42\n  b = \"hello, world\"\n}", scopeval.ToString(false));
+  EXPECT_TRUE(scopeval == scopeval);
+
+  Scope* inner_scope = new Scope(setup.settings());
+  Value inner_scopeval(nullptr, std::unique_ptr<Scope>(inner_scope));
+  inner_scope->SetValue("d", Value(nullptr, static_cast<int64_t>(42)), nullptr);
+  scope->SetValue("c", inner_scopeval, nullptr);
+
+  // Test inner scope equality.
+  EXPECT_TRUE(scopeval == scopeval);
+
+  // Nested scopes should not be equal.
+  Scope* nested_scope = new Scope(scope);
+  Value nested_scopeval(nullptr, std::unique_ptr<Scope>(nested_scope));
+  EXPECT_FALSE(nested_scopeval == nested_scopeval);
+}
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
new file mode 100644 (file)
index 0000000..d5c5e45
--- /dev/null
@@ -0,0 +1,2342 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/variables.h"
+
+#include "gn/rust_variables.h"
+#include "gn/swift_variables.h"
+
+namespace variables {
+
+// Built-in variables ----------------------------------------------------------
+
+const char kGnVersion[] = "gn_version";
+const char kGnVersion_HelpShort[] =
+    "gn_version: [number] The version of gn.";
+const char kGnVersion_Help[] =
+  R"(gn_version: [number] The version of gn.
+
+  Corresponds to the number printed by `gn --version`.
+
+Example
+
+  assert(gn_version >= 1700, "need GN version 1700 for the frobulate feature")
+)";
+
+const char kHostCpu[] = "host_cpu";
+const char kHostCpu_HelpShort[] =
+    "host_cpu: [string] The processor architecture that GN is running on.";
+const char kHostCpu_Help[] =
+    R"(host_cpu: The processor architecture that GN is running on.
+
+  This is value is exposed so that cross-compile toolchains can access the host
+  architecture when needed.
+
+  The value should generally be considered read-only, but it can be overridden
+  in order to handle unusual cases where there might be multiple plausible
+  values for the host architecture (e.g., if you can do either 32-bit or 64-bit
+  builds). The value is not used internally by GN for any purpose.
+
+Some possible values
+
+  - "x64"
+  - "x86"
+)";
+
+const char kHostOs[] = "host_os";
+const char kHostOs_HelpShort[] =
+    "host_os: [string] The operating system that GN is running on.";
+const char kHostOs_Help[] =
+    R"(host_os: [string] The operating system that GN is running on.
+
+  This value is exposed so that cross-compiles can access the host build
+  system's settings.
+
+  This value should generally be treated as read-only. It, however, is not used
+  internally by GN for any purpose.
+
+Some possible values
+
+  - "linux"
+  - "mac"
+  - "win"
+)";
+
+const char kInvoker[] = "invoker";
+const char kInvoker_HelpShort[] =
+    "invoker: [string] The invoking scope inside a template.";
+const char kInvoker_Help[] =
+    R"(invoker: [string] The invoking scope inside a template.
+
+  Inside a template invocation, this variable refers to the scope of the
+  invoker of the template. Outside of template invocations, this variable is
+  undefined.
+
+  All of the variables defined inside the template invocation are accessible as
+  members of the "invoker" scope. This is the way that templates read values
+  set by the callers.
+
+  This is often used with "defined" to see if a value is set on the invoking
+  scope.
+
+  See "gn help template" for more examples.
+
+Example
+
+  template("my_template") {
+    print(invoker.sources)       # Prints [ "a.cc", "b.cc" ]
+    print(defined(invoker.foo))  # Prints false.
+    print(defined(invoker.bar))  # Prints true.
+  }
+
+  my_template("doom_melon") {
+    sources = [ "a.cc", "b.cc" ]
+    bar = 123
+  }
+)";
+
+const char kTargetCpu[] = "target_cpu";
+const char kTargetCpu_HelpShort[] =
+    "target_cpu: [string] The desired cpu architecture for the build.";
+const char kTargetCpu_Help[] =
+    R"(target_cpu: The desired cpu architecture for the build.
+
+  This value should be used to indicate the desired architecture for the
+  primary objects of the build. It will match the cpu architecture of the
+  default toolchain, but not necessarily the current toolchain.
+
+  In many cases, this is the same as "host_cpu", but in the case of
+  cross-compiles, this can be set to something different. This value is
+  different from "current_cpu" in that it does not change based on the current
+  toolchain. When writing rules, "current_cpu" should be used rather than
+  "target_cpu" most of the time.
+
+  This value is not used internally by GN for any purpose, so it may be set to
+  whatever value is needed for the build. GN defaults this value to the empty
+  string ("") and the configuration files should set it to an appropriate value
+  (e.g., setting it to the value of "host_cpu") if it is not overridden on the
+  command line or in the args.gn file.
+
+Possible values
+
+  - "x86"
+  - "x64"
+  - "arm"
+  - "arm64"
+  - "mipsel"
+)";
+
+const char kTargetName[] = "target_name";
+const char kTargetName_HelpShort[] =
+    "target_name: [string] The name of the current target.";
+const char kTargetName_Help[] =
+    R"(target_name: [string] The name of the current target.
+
+  Inside a target or template invocation, this variable refers to the name
+  given to the target or template invocation. Outside of these, this variable
+  is undefined.
+
+  This is most often used in template definitions to name targets defined in
+  the template based on the name of the invocation. This is necessary both to
+  ensure generated targets have unique names and to generate a target with the
+  exact name of the invocation that other targets can depend on.
+
+  Be aware that this value will always reflect the innermost scope. So when
+  defining a target inside a template, target_name will refer to the target
+  rather than the template invocation. To get the name of the template
+  invocation in this case, you should save target_name to a temporary variable
+  outside of any target definitions.
+
+  See "gn help template" for more examples.
+
+Example
+
+  executable("doom_melon") {
+    print(target_name)    # Prints "doom_melon".
+  }
+
+  template("my_template") {
+    print(target_name)    # Prints "space_ray" when invoked below.
+
+    executable(target_name + "_impl") {
+      print(target_name)  # Prints "space_ray_impl".
+    }
+  }
+
+  my_template("space_ray") {
+  }
+)";
+
+const char kTargetOs[] = "target_os";
+const char kTargetOs_HelpShort[] =
+    "target_os: [string] The desired operating system for the build.";
+const char kTargetOs_Help[] =
+    R"(target_os: The desired operating system for the build.
+
+  This value should be used to indicate the desired operating system for the
+  primary object(s) of the build. It will match the OS of the default
+  toolchain.
+
+  In many cases, this is the same as "host_os", but in the case of
+  cross-compiles, it may be different. This variable differs from "current_os"
+  in that it can be referenced from inside any toolchain and will always return
+  the initial value.
+
+  This should be set to the most specific value possible. So, "android" or
+  "chromeos" should be used instead of "linux" where applicable, even though
+  Android and ChromeOS are both Linux variants. This can mean that one needs to
+  write
+
+      if (target_os == "android" || target_os == "linux") {
+          # ...
+      }
+
+  and so forth.
+
+  This value is not used internally by GN for any purpose, so it may be set to
+  whatever value is needed for the build. GN defaults this value to the empty
+  string ("") and the configuration files should set it to an appropriate value
+  (e.g., setting it to the value of "host_os") if it is not set via the command
+  line or in the args.gn file.
+
+Possible values
+
+  - "android"
+  - "chromeos"
+  - "ios"
+  - "linux"
+  - "nacl"
+  - "mac"
+  - "win"
+)";
+
+const char kCurrentCpu[] = "current_cpu";
+const char kCurrentCpu_HelpShort[] =
+    "current_cpu: [string] The processor architecture of the current "
+    "toolchain.";
+const char kCurrentCpu_Help[] =
+    R"(current_cpu: The processor architecture of the current toolchain.
+
+  The build configuration usually sets this value based on the value of
+  "host_cpu" (see "gn help host_cpu") and then threads this through the
+  toolchain definitions to ensure that it always reflects the appropriate
+  value.
+
+  This value is not used internally by GN for any purpose. It is set to the
+  empty string ("") by default but is declared so that it can be overridden on
+  the command line if so desired.
+
+  See "gn help target_cpu" for a list of common values returned.)";
+
+const char kCurrentOs[] = "current_os";
+const char kCurrentOs_HelpShort[] =
+    "current_os: [string] The operating system of the current toolchain.";
+const char kCurrentOs_Help[] =
+    R"(current_os: The operating system of the current toolchain.
+
+  The build configuration usually sets this value based on the value of
+  "target_os" (see "gn help target_os"), and then threads this through the
+  toolchain definitions to ensure that it always reflects the appropriate
+  value.
+
+  This value is not used internally by GN for any purpose. It is set to the
+  empty string ("") by default but is declared so that it can be overridden on
+  the command line if so desired.
+
+  See "gn help target_os" for a list of common values returned.
+)";
+
+const char kCurrentToolchain[] = "current_toolchain";
+const char kCurrentToolchain_HelpShort[] =
+    "current_toolchain: [string] Label of the current toolchain.";
+const char kCurrentToolchain_Help[] =
+    R"(current_toolchain: Label of the current toolchain.
+
+  A fully-qualified label representing the current toolchain. You can use this
+  to make toolchain-related decisions in the build. See also
+  "default_toolchain".
+
+Example
+
+  if (current_toolchain == "//build:64_bit_toolchain") {
+    executable("output_thats_64_bit_only") {
+      ...
+)";
+
+const char kDefaultToolchain[] = "default_toolchain";
+const char kDefaultToolchain_HelpShort[] =
+    "default_toolchain: [string] Label of the default toolchain.";
+const char kDefaultToolchain_Help[] =
+    R"(default_toolchain: [string] Label of the default toolchain.
+
+  A fully-qualified label representing the default toolchain, which may not
+  necessarily be the current one (see "current_toolchain").
+)";
+
+const char kPythonPath[] = "python_path";
+const char kPythonPath_HelpShort[] =
+    "python_path: [string] Absolute path of Python.";
+const char kPythonPath_Help[] =
+    R"(python_path: Absolute path of Python.
+
+  Normally used in toolchain definitions if running some command requires
+  Python. You will normally not need this when invoking scripts since GN
+  automatically finds it for you.
+)";
+
+const char kRootBuildDir[] = "root_build_dir";
+const char kRootBuildDir_HelpShort[] =
+    "root_build_dir: [string] Directory where build commands are run.";
+const char kRootBuildDir_Help[] =
+    R"(root_build_dir: [string] Directory where build commands are run.
+
+  This is the root build output directory which will be the current directory
+  when executing all compilers and scripts.
+
+  Most often this is used with rebase_path (see "gn help rebase_path") to
+  convert arguments to be relative to a script's current directory.
+)";
+
+const char kRootGenDir[] = "root_gen_dir";
+const char kRootGenDir_HelpShort[] =
+    "root_gen_dir: [string] Directory for the toolchain's generated files.";
+const char kRootGenDir_Help[] =
+    R"(root_gen_dir: Directory for the toolchain's generated files.
+
+  Absolute path to the root of the generated output directory tree for the
+  current toolchain. An example would be "//out/Debug/gen" for the default
+  toolchain, or "//out/Debug/arm/gen" for the "arm" toolchain.
+
+  This is primarily useful for setting up include paths for generated files. If
+  you are passing this to a script, you will want to pass it through
+  rebase_path() (see "gn help rebase_path") to convert it to be relative to the
+  build directory.
+
+  See also "target_gen_dir" which is usually a better location for generated
+  files. It will be inside the root generated dir.
+)";
+
+const char kRootOutDir[] = "root_out_dir";
+const char kRootOutDir_HelpShort[] =
+    "root_out_dir: [string] Root directory for toolchain output files.";
+const char kRootOutDir_Help[] =
+    R"(root_out_dir: [string] Root directory for toolchain output files.
+
+  Absolute path to the root of the output directory tree for the current
+  toolchain. It will not have a trailing slash.
+
+  For the default toolchain this will be the same as the root_build_dir. An
+  example would be "//out/Debug" for the default toolchain, or
+  "//out/Debug/arm" for the "arm" toolchain.
+
+  This is primarily useful for setting up script calls. If you are passing this
+  to a script, you will want to pass it through rebase_path() (see "gn help
+  rebase_path") to convert it to be relative to the build directory.
+
+  See also "target_out_dir" which is usually a better location for output
+  files. It will be inside the root output dir.
+
+Example
+
+  action("myscript") {
+    # Pass the output dir to the script.
+    args = [ "-o", rebase_path(root_out_dir, root_build_dir) ]
+  }
+)";
+
+const char kTargetGenDir[] = "target_gen_dir";
+const char kTargetGenDir_HelpShort[] =
+    "target_gen_dir: [string] Directory for a target's generated files.";
+const char kTargetGenDir_Help[] =
+    R"(target_gen_dir: Directory for a target's generated files.
+
+  Absolute path to the target's generated file directory. This will be the
+  "root_gen_dir" followed by the relative path to the current build file. If
+  your file is in "//tools/doom_melon" then target_gen_dir would be
+  "//out/Debug/gen/tools/doom_melon". It will not have a trailing slash.
+
+  This is primarily useful for setting up include paths for generated files. If
+  you are passing this to a script, you will want to pass it through
+  rebase_path() (see "gn help rebase_path") to convert it to be relative to the
+  build directory.
+
+  See also "gn help root_gen_dir".
+
+Example
+
+  action("myscript") {
+    # Pass the generated output dir to the script.
+    args = [ "-o", rebase_path(target_gen_dir, root_build_dir) ]
+  }
+)";
+
+const char kTargetOutDir[] = "target_out_dir";
+const char kTargetOutDir_HelpShort[] =
+    "target_out_dir: [string] Directory for target output files.";
+const char kTargetOutDir_Help[] =
+    R"(target_out_dir: [string] Directory for target output files.
+
+  Absolute path to the target's generated file directory. If your current
+  target is in "//tools/doom_melon" then this value might be
+  "//out/Debug/obj/tools/doom_melon". It will not have a trailing slash.
+
+  This is primarily useful for setting up arguments for calling scripts. If you
+  are passing this to a script, you will want to pass it through rebase_path()
+  (see "gn help rebase_path") to convert it to be relative to the build
+  directory.
+
+  See also "gn help root_out_dir".
+
+Example
+
+  action("myscript") {
+    # Pass the output dir to the script.
+    args = [ "-o", rebase_path(target_out_dir, root_build_dir) ]
+  }
+)";
+
+// Target variables ------------------------------------------------------------
+
+#define COMMON_ORDERING_HELP                                                 \
+  "\n"                                                                       \
+  "Ordering of flags and values\n"                                           \
+  "\n"                                                                       \
+  "  1. Those set on the current target (not in a config).\n"                \
+  "  2. Those set on the \"configs\" on the target in order that the\n"      \
+  "     configs appear in the list.\n"                                       \
+  "  3. Those set on the \"all_dependent_configs\" on the target in order\n" \
+  "     that the configs appear in the list.\n"                              \
+  "  4. Those set on the \"public_configs\" on the target in order that\n"   \
+  "     those configs appear in the list.\n"                                 \
+  "  5. all_dependent_configs pulled from dependencies, in the order of\n"   \
+  "     the \"deps\" list. This is done recursively. If a config appears\n"  \
+  "     more than once, only the first occurrence will be used.\n"            \
+  "  6. public_configs pulled from dependencies, in the order of the\n"      \
+  "     \"deps\" list. If a dependency is public, they will be applied\n"    \
+  "     recursively.\n"
+
+const char kAllDependentConfigs[] = "all_dependent_configs";
+const char kAllDependentConfigs_HelpShort[] =
+    "all_dependent_configs: [label list] Configs to be forced on dependents.";
+const char kAllDependentConfigs_Help[] =
+    R"(all_dependent_configs: Configs to be forced on dependents.
+
+  A list of config labels.
+
+  All targets depending on this one, and recursively, all targets depending on
+  those, will have the configs listed in this variable added to them. These
+  configs will also apply to the current target.
+
+  This addition happens in a second phase once a target and all of its
+  dependencies have been resolved. Therefore, a target will not see these
+  force-added configs in their "configs" variable while the script is running,
+  and they can not be removed. As a result, this capability should generally
+  only be used to add defines and include directories necessary to compile a
+  target's headers.
+
+  See also "public_configs".
+)" COMMON_ORDERING_HELP;
+
+const char kAllowCircularIncludesFrom[] = "allow_circular_includes_from";
+const char kAllowCircularIncludesFrom_HelpShort[] =
+    "allow_circular_includes_from: [label list] Permit includes from deps.";
+const char kAllowCircularIncludesFrom_Help[] =
+    R"(allow_circular_includes_from: Permit includes from deps.
+
+  A list of target labels. Must be a subset of the target's "deps". These
+  targets will be permitted to include headers from the current target despite
+  the dependency going in the opposite direction.
+
+  When you use this, both targets must be included in a final binary for it to
+  link. To keep linker errors from happening, it is good practice to have all
+  external dependencies depend only on one of the two targets, and to set the
+  visibility on the other to enforce this. Thus the targets will always be
+  linked together in any output.
+
+Details
+
+  Normally, for a file in target A to include a file from target B, A must list
+  B as a dependency. This invariant is enforced by the "gn check" command (and
+  the --check flag to "gn gen" -- see "gn help check").
+
+  Sometimes, two targets might be the same unit for linking purposes (two
+  source sets or static libraries that would always be linked together in a
+  final executable or shared library) and they each include headers from the
+  other: you want A to be able to include B's headers, and B to include A's
+  headers. This is not an ideal situation but is sometimes unavoidable.
+
+  This list, if specified, lists which of the dependencies of the current
+  target can include header files from the current target. That is, if A
+  depends on B, B can only include headers from A if it is in A's
+  allow_circular_includes_from list. Normally includes must follow the
+  direction of dependencies, this flag allows them to go in the opposite
+  direction.
+
+Danger
+
+  In the above example, A's headers are likely to include headers from A's
+  dependencies. Those dependencies may have public_configs that apply flags,
+  defines, and include paths that make those headers work properly.
+
+  With allow_circular_includes_from, B can include A's headers, and
+  transitively from A's dependencies, without having the dependencies that
+  would bring in the public_configs those headers need. The result may be
+  errors or inconsistent builds.
+
+  So when you use allow_circular_includes_from, make sure that any compiler
+  settings, flags, and include directories are the same between both targets
+  (consider putting such things in a shared config they can both reference).
+  Make sure the dependencies are also the same (you might consider a group to
+  collect such dependencies they both depend on).
+
+Example
+
+  source_set("a") {
+    deps = [ ":b", ":a_b_shared_deps" ]
+    allow_circular_includes_from = [ ":b" ]
+    ...
+  }
+
+  source_set("b") {
+    deps = [ ":a_b_shared_deps" ]
+    # Sources here can include headers from a despite lack of deps.
+    ...
+  }
+
+  group("a_b_shared_deps") {
+    public_deps = [ ":c" ]
+  }
+)";
+
+const char kArflags[] = "arflags";
+const char kArflags_HelpShort[] =
+    "arflags: [string list] Arguments passed to static_library archiver.";
+const char kArflags_Help[] =
+    R"(arflags: Arguments passed to static_library archiver.
+
+  A list of flags passed to the archive/lib command that creates static
+  libraries.
+
+  arflags are NOT pushed to dependents, so applying arflags to source sets or
+  any other target type will be a no-op. As with ldflags, you could put the
+  arflags in a config and set that as a public or "all dependent" config, but
+  that will likely not be what you want. If you have a chain of static
+  libraries dependent on each other, this can cause the flags to propagate up
+  to other static libraries. Due to the nature of how arflags are typically
+  used, you will normally want to apply them directly on static_library targets
+  themselves.
+)" COMMON_ORDERING_HELP;
+
+const char kArgs[] = "args";
+const char kArgs_HelpShort[] =
+    "args: [string list] Arguments passed to an action.";
+const char kArgs_Help[] =
+    R"(args: (target variable) Arguments passed to an action.
+
+  For action and action_foreach targets, args is the list of arguments to pass
+  to the script. Typically you would use source expansion (see "gn help
+  source_expansion") to insert the source file names.
+
+  See also "gn help action" and "gn help action_foreach".
+)";
+
+const char kAssertNoDeps[] = "assert_no_deps";
+const char kAssertNoDeps_HelpShort[] =
+    "assert_no_deps: [label pattern list] Ensure no deps on these targets.";
+const char kAssertNoDeps_Help[] =
+    R"(assert_no_deps: Ensure no deps on these targets.
+
+  A list of label patterns.
+
+  This list is a list of patterns that must not match any of the transitive
+  dependencies of the target. These include all public, private, and data
+  dependencies, and cross shared library boundaries. This allows you to express
+  that undesirable code isn't accidentally added to downstream dependencies in
+  a way that might otherwise be difficult to notice.
+
+  Checking does not cross executable boundaries. If a target depends on an
+  executable, it's assumed that the executable is a tool that is producing part
+  of the build rather than something that is linked and distributed. This
+  allows assert_no_deps to express what is distributed in the final target
+  rather than depend on the internal build steps (which may include
+  non-distributable code).
+
+  See "gn help label_pattern" for the format of the entries in the list. These
+  patterns allow blacklisting individual targets or whole directory
+  hierarchies.
+
+  Sometimes it is desirable to enforce that many targets have no dependencies
+  on a target or set of targets. One efficient way to express this is to create
+  a group with the assert_no_deps rule on it, and make that group depend on all
+  targets you want to apply that assertion to.
+
+Example
+
+  executable("doom_melon") {
+    deps = [ "//foo:bar" ]
+    ...
+    assert_no_deps = [
+      "//evil/*",  # Don't link any code from the evil directory.
+      "//foo:test_support",  # This target is also disallowed.
+    ]
+  }
+)";
+
+const char kBundleRootDir[] = "bundle_root_dir";
+const char kBundleRootDir_HelpShort[] =
+    "bundle_root_dir: Expansion of {{bundle_root_dir}} in create_bundle.";
+const char kBundleRootDir_Help[] =
+    R"(bundle_root_dir: Expansion of {{bundle_root_dir}} in create_bundle.
+
+  A string corresponding to a path in root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_root_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under root_build_dir.
+
+Example
+
+  bundle_data("info_plist") {
+    sources = [ "Info.plist" ]
+    outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
+  }
+
+  create_bundle("doom_melon.app") {
+    deps = [ ":info_plist" ]
+    bundle_root_dir = "${root_build_dir}/doom_melon.app"
+    bundle_contents_dir = "${bundle_root_dir}/Contents"
+    bundle_resources_dir = "${bundle_contents_dir}/Resources"
+    bundle_executable_dir = "${bundle_contents_dir}/MacOS"
+  }
+)";
+
+const char kBundleContentsDir[] = "bundle_contents_dir";
+const char kBundleContentsDir_HelpShort[] =
+    "bundle_contents_dir: "
+    "Expansion of {{bundle_contents_dir}} in create_bundle.";
+const char kBundleContentsDir_Help[] =
+    R"(bundle_contents_dir: Expansion of {{bundle_contents_dir}} in
+                             create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_contents_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+)";
+
+const char kBundleResourcesDir[] = "bundle_resources_dir";
+const char kBundleResourcesDir_HelpShort[] =
+    "bundle_resources_dir: "
+    "Expansion of {{bundle_resources_dir}} in create_bundle.";
+const char kBundleResourcesDir_Help[] =
+    R"(bundle_resources_dir
+
+  bundle_resources_dir: Expansion of {{bundle_resources_dir}} in
+                        create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_resources_dir}} of the "bundle_data" target it depends on. This must
+  correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+)";
+
+const char kBundleDepsFilter[] = "bundle_deps_filter";
+const char kBundleDepsFilter_HelpShort[] =
+    "bundle_deps_filter: [label list] A list of labels that are filtered out.";
+const char kBundleDepsFilter_Help[] =
+    R"(bundle_deps_filter: [label list] A list of labels that are filtered out.
+
+  A list of target labels.
+
+  This list contains target label patterns that should be filtered out when
+  creating the bundle. Any target matching one of those label will be removed
+  from the dependencies of the create_bundle target.
+
+  This is mostly useful when creating application extension bundle as the
+  application extension has access to runtime resources from the application
+  bundle and thus do not require a second copy.
+
+  See "gn help create_bundle" for more information.
+
+Example
+
+  create_bundle("today_extension") {
+    deps = [
+      "//base"
+    ]
+    bundle_root_dir = "$root_out_dir/today_extension.appex"
+    bundle_deps_filter = [
+      # The extension uses //base but does not use any function calling into
+      # third_party/icu and thus does not need the icudtl.dat file.
+      "//third_party/icu:icudata",
+    ]
+  }
+)";
+
+const char kBundleExecutableDir[] = "bundle_executable_dir";
+const char kBundleExecutableDir_HelpShort[] =
+    "bundle_executable_dir: "
+    "Expansion of {{bundle_executable_dir}} in create_bundle";
+const char kBundleExecutableDir_Help[] =
+    R"(bundle_executable_dir
+
+  bundle_executable_dir: Expansion of {{bundle_executable_dir}} in
+                         create_bundle.
+
+  A string corresponding to a path in $root_build_dir.
+
+  This string is used by the "create_bundle" target to expand the
+  {{bundle_executable_dir}} of the "bundle_data" target it depends on. This
+  must correspond to a path under "bundle_root_dir".
+
+  See "gn help bundle_root_dir" for examples.
+)";
+
+const char kXcassetCompilerFlags[] = "xcasset_compiler_flags";
+const char kXcassetCompilerFlags_HelpShort[] =
+    "xcasset_compiler_flags: [string list] Flags passed to xcassets compiler";
+const char kXcassetCompilerFlags_Help[] =
+    R"(xcasset_compiler_flags: Flags passed to xcassets compiler.
+
+  A list of strings.
+
+  Valid for create_bundle target. Those flags are directly passed to
+  xcassets compiler, corresponding to {{xcasset_compiler_flags}} substitution
+  in compile_xcassets tool.
+)";
+
+const char kCflags[] = "cflags";
+const char kCflags_HelpShort[] =
+    "cflags: [string list] Flags passed to all C compiler variants.";
+const char kCommonCflagsHelp[] =
+    R"(cflags*: Flags passed to the C compiler.
+
+  A list of strings.
+
+  "cflags" are passed to all invocations of the C, C++, Objective C, and
+  Objective C++ compilers.
+
+  To target one of these variants individually, use "cflags_c", "cflags_cc",
+  "cflags_objc", and "cflags_objcc", respectively. These variant-specific
+  versions of cflags* will be appended on the compiler command line after
+  "cflags".
+
+  See also "asmflags" for flags for assembly-language files and "swiftflags"
+  for swift files.
+)" COMMON_ORDERING_HELP;
+const char* kCflags_Help = kCommonCflagsHelp;
+
+const char kAsmflags[] = "asmflags";
+const char kAsmflags_HelpShort[] =
+    "asmflags: [string list] Flags passed to the assembler.";
+const char* kAsmflags_Help =
+    R"(asmflags: Flags passed to the assembler.
+
+  A list of strings.
+
+  "asmflags" are passed to any invocation of a tool that takes an .asm or .S
+  file as input.
+)" COMMON_ORDERING_HELP;
+
+const char kCflagsC[] = "cflags_c";
+const char kCflagsC_HelpShort[] =
+    "cflags_c: [string list] Flags passed to the C compiler.";
+const char* kCflagsC_Help = kCommonCflagsHelp;
+
+const char kCflagsCC[] = "cflags_cc";
+const char kCflagsCC_HelpShort[] =
+    "cflags_cc: [string list] Flags passed to the C++ compiler.";
+const char* kCflagsCC_Help = kCommonCflagsHelp;
+
+const char kCflagsObjC[] = "cflags_objc";
+const char kCflagsObjC_HelpShort[] =
+    "cflags_objc: [string list] Flags passed to the Objective C compiler.";
+const char* kCflagsObjC_Help = kCommonCflagsHelp;
+
+const char kCflagsObjCC[] = "cflags_objcc";
+const char kCflagsObjCC_HelpShort[] =
+    "cflags_objcc: [string list] Flags passed to the Objective C++ compiler.";
+const char* kCflagsObjCC_Help = kCommonCflagsHelp;
+
+const char kCheckIncludes[] = "check_includes";
+const char kCheckIncludes_HelpShort[] =
+    "check_includes: [boolean] Controls whether a target's files are checked.";
+const char kCheckIncludes_Help[] =
+    R"(check_includes: [boolean] Controls whether a target's files are checked.
+
+  When true (the default), the "gn check" command (as well as "gn gen" with the
+  --check flag) will check this target's sources and headers for proper
+  dependencies.
+
+  When false, the files in this target will be skipped by default. This does
+  not affect other targets that depend on the current target, it just skips
+  checking the includes of the current target's files.
+
+  If there are a few conditionally included headers that trip up checking, you
+  can exclude headers individually by annotating them with "nogncheck" (see "gn
+  help nogncheck").
+
+  The topic "gn help check" has general information on how checking works and
+  advice on how to pass a check in problematic cases.
+
+Example
+
+  source_set("busted_includes") {
+    # This target's includes are messed up, exclude it from checking.
+    check_includes = false
+    ...
+  }
+)";
+
+const char kCodeSigningArgs[] = "code_signing_args";
+const char kCodeSigningArgs_HelpShort[] =
+    "code_signing_args: [string list] Arguments passed to code signing script.";
+const char kCodeSigningArgs_Help[] =
+    R"(code_signing_args: [string list] Arguments passed to code signing script.
+
+  For create_bundle targets, code_signing_args is the list of arguments to pass
+  to the code signing script. Typically you would use source expansion (see "gn
+  help source_expansion") to insert the source file names.
+
+  See also "gn help create_bundle".
+)";
+
+const char kCodeSigningScript[] = "code_signing_script";
+const char kCodeSigningScript_HelpShort[] =
+    "code_signing_script: [file name] Script for code signing.";
+const char kCodeSigningScript_Help[] =
+    R"(code_signing_script: [file name] Script for code signing."
+
+  An absolute or buildfile-relative file name of a Python script to run for a
+  create_bundle target to perform code signing step.
+
+  See also "gn help create_bundle".
+)";
+
+const char kCodeSigningSources[] = "code_signing_sources";
+const char kCodeSigningSources_HelpShort[] =
+    "code_signing_sources: [file list] Sources for code signing step.";
+const char kCodeSigningSources_Help[] =
+    R"(code_signing_sources: [file list] Sources for code signing step.
+
+  A list of files used as input for code signing script step of a create_bundle
+  target. Non-absolute paths will be resolved relative to the current build
+  file.
+
+  See also "gn help create_bundle".
+)";
+
+const char kCodeSigningOutputs[] = "code_signing_outputs";
+const char kCodeSigningOutputs_HelpShort[] =
+    "code_signing_outputs: [file list] Output files for code signing step.";
+const char kCodeSigningOutputs_Help[] =
+    R"(code_signing_outputs: [file list] Output files for code signing step.
+
+  Outputs from the code signing step of a create_bundle target. Must refer to
+  files in the build directory.
+
+  See also "gn help create_bundle".
+)";
+
+const char kCompleteStaticLib[] = "complete_static_lib";
+const char kCompleteStaticLib_HelpShort[] =
+    "complete_static_lib: [boolean] Links all deps into a static library.";
+const char kCompleteStaticLib_Help[] =
+    R"(complete_static_lib: [boolean] Links all deps into a static library.
+
+  A static library normally doesn't include code from dependencies, but instead
+  forwards the static libraries and source sets in its deps up the dependency
+  chain until a linkable target (an executable or shared library) is reached.
+  The final linkable target only links each static library once, even if it
+  appears more than once in its dependency graph.
+
+  In some cases the static library might be the final desired output. For
+  example, you may be producing a static library for distribution to third
+  parties. In this case, the static library should include code for all
+  dependencies in one complete package. However, complete static libraries
+  themselves are never linked into other complete static libraries. All
+  complete static libraries are for distribution and linking them in would
+  cause code duplication in this case. If the static library is not for
+  distribution, it should not be complete.
+
+  GN treats non-complete static libraries as source sets when they are linked
+  into complete static libraries. This is done because some tools like AR do
+  not handle dependent static libraries properly. This makes it easier to write
+  "alink" rules.
+
+  In rare cases it makes sense to list a header in more than one target if it
+  could be considered conceptually a member of both. libraries.
+
+Example
+
+  static_library("foo") {
+    complete_static_lib = true
+    deps = [ "bar" ]
+  }
+)";
+
+const char kConfigs[] = "configs";
+const char kConfigs_HelpShort[] =
+    "configs: [label list] Configs applying to this target or config.";
+const char kConfigs_Help[] =
+    R"(configs: Configs applying to this target or config.
+
+  A list of config labels.
+
+Configs on a target
+
+  When used on a target, the include_dirs, defines, etc. in each config are
+  appended in the order they appear to the compile command for each file in the
+  target. They will appear after the include_dirs, defines, etc. that the
+  target sets directly.
+
+  Since configs apply after the values set on a target, directly setting a
+  compiler flag will prepend it to the command line. If you want to append a
+  flag instead, you can put that flag in a one-off config and append that
+  config to the target's configs list.
+
+  The build configuration script will generally set up the default configs
+  applying to a given target type (see "set_defaults"). When a target is being
+  defined, it can add to or remove from this list.
+
+Configs on a config
+
+  It is possible to create composite configs by specifying configs on a config.
+  One might do this to forward values, or to factor out blocks of settings from
+  very large configs into more manageable named chunks.
+
+  In this case, the composite config is expanded to be the concatenation of its
+  own values, and in order, the values from its sub-configs *before* anything
+  else happens. This has some ramifications:
+
+   - A target has no visibility into a config's sub-configs. Target code only
+     sees the name of the composite config. It can't remove sub-configs or opt
+     in to only parts of it. The composite config may not even be defined
+     before the target is.
+
+   - You can get duplication of values if a config is listed twice, say, on a
+     target and in a sub-config that also applies. In other cases, the configs
+     applying to a target are de-duped. It's expected that if a config is
+     listed as a sub-config that it is only used in that context. (Note that
+     it's possible to fix this and de-dupe, but it's not normally relevant and
+     complicates the implementation.)
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  # Configs on a target.
+  source_set("foo") {
+    # Don't use the default RTTI config that BUILDCONFIG applied to us.
+    configs -= [ "//build:no_rtti" ]
+
+    # Add some of our own settings.
+    configs += [ ":mysettings" ]
+  }
+
+  # Create a default_optimization config that forwards to one of a set of more
+  # specialized configs depending on build flags. This pattern is useful
+  # because it allows a target to opt in to either a default set, or a more
+  # specific set, while avoid duplicating the settings in two places.
+  config("super_optimization") {
+    cflags = [ ... ]
+  }
+  config("default_optimization") {
+    if (optimize_everything) {
+      configs = [ ":super_optimization" ]
+    } else {
+      configs = [ ":no_optimization" ]
+    }
+  }
+)";
+
+const char kData[] = "data";
+const char kData_HelpShort[] =
+    "data: [file list] Runtime data file dependencies.";
+const char kData_Help[] =
+    R"(data: Runtime data file dependencies.
+
+  Lists files or directories required to run the given target. These are
+  typically data files or directories of data files. The paths are interpreted
+  as being relative to the current build file. Since these are runtime
+  dependencies, they do not affect which targets are built or when. To declare
+  input files to a script, use "inputs".
+
+  Appearing in the "data" section does not imply any special handling such as
+  copying them to the output directory. This is just used for declaring runtime
+  dependencies. Runtime dependencies can be queried using the "runtime_deps"
+  category of "gn desc" or written during build generation via
+  "--runtime-deps-list-file".
+
+  GN doesn't require data files to exist at build-time. So actions that produce
+  files that are in turn runtime dependencies can list those generated files
+  both in the "outputs" list as well as the "data" list.
+
+  By convention, directories are listed with a trailing slash:
+    data = [ "test/data/" ]
+  However, no verification is done on these so GN doesn't enforce this. The
+  paths are just rebased and passed along when requested.
+
+  Note: On iOS and macOS, create_bundle targets will not be recursed into when
+  gathering data. See "gn help create_bundle" for details.
+
+  See "gn help runtime_deps" for how these are used.
+)";
+
+const char kDataDeps[] = "data_deps";
+const char kDataDeps_HelpShort[] =
+    "data_deps: [label list] Non-linked dependencies.";
+const char kDataDeps_Help[] =
+    R"(data_deps: Non-linked dependencies.
+
+  A list of target labels.
+
+  Specifies dependencies of a target that are not actually linked into the
+  current target. Such dependencies will be built and will be available at
+  runtime.
+
+  This is normally used for things like plugins or helper programs that a
+  target needs at runtime.
+
+  Note: On iOS and macOS, create_bundle targets will not be recursed into when
+  gathering data_deps. See "gn help create_bundle" for details.
+
+  See also "gn help deps" and "gn help data".
+
+Example
+
+  executable("foo") {
+    deps = [ "//base" ]
+    data_deps = [ "//plugins:my_runtime_plugin" ]
+  }
+)";
+
+const char kDataKeys[] = "data_keys";
+const char kDataKeys_HelpShort[] =
+    "data_keys: [string list] Keys from which to collect metadata.";
+const char kDataKeys_Help[] =
+    R"(data_keys: Keys from which to collect metadata.
+
+  These keys are used to identify metadata to collect. If a walked target
+  defines this key in its metadata, its value will be appended to the resulting
+  collection.
+
+  See "gn help generated_file".
+)";
+
+const char kDefines[] = "defines";
+const char kDefines_HelpShort[] =
+    "defines: [string list] C preprocessor defines.";
+const char kDefines_Help[] =
+    R"(defines: C preprocessor defines.
+
+  A list of strings
+
+  These strings will be passed to the C/C++ compiler as #defines. The strings
+  may or may not include an "=" to assign a value.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  defines = [ "AWESOME_FEATURE", "LOG_LEVEL=3" ]
+)";
+
+const char kDepfile[] = "depfile";
+const char kDepfile_HelpShort[] =
+    "depfile: [string] File name for input dependencies for actions.";
+const char kDepfile_Help[] =
+    R"(depfile: [string] File name for input dependencies for actions.
+
+  If nonempty, this string specifies that the current action or action_foreach
+  target will generate the given ".d" file containing the dependencies of the
+  input. Empty or unset means that the script doesn't generate the files.
+
+  A depfile should be used only when a target depends on files that are not
+  already specified by a target's inputs and sources. Likewise, depfiles should
+  specify only those dependencies not already included in sources or inputs.
+
+  The .d file should go in the target output directory. If you have more than
+  one source file that the script is being run over, you can use the output
+  file expansions described in "gn help action_foreach" to name the .d file
+  according to the input.
+
+  The format is that of a Makefile and all paths must be relative to the root
+  build directory. Only one output may be listed and it must match the first
+  output of the action.
+
+  Although depfiles are created by an action, they should not be listed in the
+  action's "outputs" unless another target will use the file as an input.
+
+Example
+
+  action_foreach("myscript_target") {
+    script = "myscript.py"
+    sources = [ ... ]
+
+    # Locate the depfile in the output directory named like the
+    # inputs but with a ".d" appended.
+    depfile = "$relative_target_output_dir/{{source_name}}.d"
+
+    # Say our script uses "-o <d file>" to indicate the depfile.
+    args = [ "{{source}}", "-o", depfile ]
+  }
+)";
+
+const char kDeps[] = "deps";
+const char kDeps_HelpShort[] =
+    "deps: [label list] Private linked dependencies.";
+const char kDeps_Help[] =
+    R"(deps: Private linked dependencies.
+
+  A list of target labels.
+
+  Specifies private dependencies of a target. Private dependencies are
+  propagated up the dependency tree and linked to dependent targets, but do not
+  grant the ability to include headers from the dependency. Public configs are
+  not forwarded.
+
+Details of dependency propagation
+
+  Source sets, shared libraries, and non-complete static libraries will be
+  propagated up the dependency tree across groups, non-complete static
+  libraries and source sets.
+
+  Executables, shared libraries, and complete static libraries will link all
+  propagated targets and stop propagation. Actions and copy steps also stop
+  propagation, allowing them to take a library as an input but not force
+  dependents to link to it.
+
+  Propagation of all_dependent_configs and public_configs happens independently
+  of target type. all_dependent_configs are always propagated across all types
+  of targets, and public_configs are always propagated across public deps of
+  all types of targets.
+
+  Data dependencies are propagated differently. See "gn help data_deps" and
+  "gn help runtime_deps".
+
+  See also "public_deps".
+)";
+
+const char kExterns[] = "externs";
+const char kExterns_HelpShort[] =
+    "externs: [scope] Set of Rust crate-dependency pairs.";
+const char kExterns_Help[] =
+    R"(externs: [scope] Set of Rust crate-dependency pairs.
+
+  A list, each value being a scope indicating a pair of crate name and the path
+  to the Rust library.
+
+  These libraries will be passed as `--extern crate_name=path` to compiler
+  invocation containing the current target.
+
+Examples
+
+    executable("foo") {
+      sources = [ "main.rs" ]
+      externs = [{
+        crate_name = "bar",
+        path = "path/to/bar.rlib"
+      }]
+    }
+
+  This target would compile the `foo` crate with the following `extern` flag:
+  `--extern bar=path/to/bar.rlib`.
+)";
+
+const char kFriend[] = "friend";
+const char kFriend_HelpShort[] =
+    "friend: [label pattern list] Allow targets to include private headers.";
+const char kFriend_Help[] =
+    R"(friend: Allow targets to include private headers.
+
+  A list of label patterns (see "gn help label_pattern") that allow dependent
+  targets to include private headers. Applies to all binary targets.
+
+  Normally if a target lists headers in the "public" list (see "gn help
+  public"), other headers are implicitly marked as private. Private headers
+  can not be included by other targets, even with a public dependency path.
+  The "gn check" function performs this validation.
+
+  A friend declaration allows one or more targets to include private headers.
+  This is useful for things like unit tests that are closely associated with a
+  target and require internal knowledge without opening up all headers to be
+  included by all dependents.
+
+  A friend target does not allow that target to include headers when no
+  dependency exists. A public dependency path must still exist between two
+  targets to include any headers from a destination target. The friend
+  annotation merely allows the use of headers that would otherwise be
+  prohibited because they are private.
+
+  The friend annotation is matched only against the target containing the file
+  with the include directive. Friend annotations are not propagated across
+  public or private dependencies. Friend annotations do not affect visibility.
+
+Example
+
+  static_library("lib") {
+    # This target can include our private headers.
+    friend = [ ":unit_tests" ]
+
+    public = [
+      "public_api.h",  # Normal public API for dependent targets.
+    ]
+
+    # Private API and sources.
+    sources = [
+      "a_source_file.cc",
+
+      # Normal targets that depend on this one won't be able to include this
+      # because this target defines a list of "public" headers. Without the
+      # "public" list, all headers are implicitly public.
+      "private_api.h",
+    ]
+  }
+
+  executable("unit_tests") {
+    sources = [
+      # This can include "private_api.h" from the :lib target because it
+      # depends on that target and because of the friend annotation.
+      "my_test.cc",
+    ]
+
+    deps = [
+      ":lib",  # Required for the include to be allowed.
+    ]
+  }
+)";
+
+const char kFrameworkDirs[] = "framework_dirs";
+const char kFrameworkDirs_HelpShort[] =
+    "framework_dirs: [directory list] Additional framework search directories.";
+const char kFrameworkDirs_Help[] =
+    R"(framework_dirs: [directory list] Additional framework search directories.
+
+  A list of source directories.
+
+  The directories in this list will be added to the framework search path for
+  the files in the affected target.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  framework_dirs = [ "src/include", "//third_party/foo" ]
+)";
+
+const char kFrameworks[] = "frameworks";
+const char kFrameworks_HelpShort[] =
+    "frameworks: [name list] Name of frameworks that must be linked.";
+const char kFrameworks_Help[] =
+    R"(frameworks: [name list] Name of frameworks that must be linked.
+
+  A list of framework names.
+
+  The frameworks named in that list will be linked with any dynamic link
+  type target.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  frameworks = [ "Foundation.framework", "Foo.framework" ]
+)";
+
+const char kIncludeDirs[] = "include_dirs";
+const char kIncludeDirs_HelpShort[] =
+    "include_dirs: [directory list] Additional include directories.";
+const char kIncludeDirs_Help[] =
+    R"(include_dirs: Additional include directories.
+
+  A list of source directories.
+
+  The directories in this list will be added to the include path for the files
+  in the affected target.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  include_dirs = [ "src/include", "//third_party/foo" ]
+)";
+
+const char kInputs[] = "inputs";
+const char kInputs_HelpShort[] =
+    "inputs: [file list] Additional compile-time dependencies.";
+const char kInputs_Help[] =
+    R"(inputs: Additional compile-time dependencies.
+
+  Inputs are compile-time dependencies of the current target. This means that
+  all inputs must be available before compiling any of the sources or executing
+  any actions.
+
+  Inputs are typically only used for action and action_foreach targets.
+
+Inputs for actions
+
+  For action and action_foreach targets, inputs should be the inputs to script
+  that don't vary. These should be all .py files that the script uses via
+  imports (the main script itself will be an implicit dependency of the action
+  so need not be listed).
+
+  For action targets, inputs and sources are treated the same, but from a style
+  perspective, it's recommended to follow the same rule as action_foreach and
+  put helper files in the inputs, and the data used by the script (if any) in
+  sources.
+
+  Note that another way to declare input dependencies from an action is to have
+  the action write a depfile (see "gn help depfile"). This allows the script to
+  dynamically write input dependencies, that might not be known until actually
+  executing the script. This is more efficient than doing processing while
+  running GN to determine the inputs, and is easier to keep in-sync than
+  hardcoding the list.
+
+Script input gotchas
+
+  It may be tempting to write a script that enumerates all files in a directory
+  as inputs. Don't do this! Even if you specify all the files in the inputs or
+  sources in the GN target (or worse, enumerate the files in an exec_script
+  call when running GN, which will be slow), the dependencies will be broken.
+
+  The problem happens if a file is ever removed because the inputs are not
+  listed on the command line to the script. Because the script hasn't changed
+  and all inputs are up to date, the script will not re-run and you will get a
+  stale build. Instead, either list all inputs on the command line to the
+  script, or if there are many, create a separate list file that the script
+  reads. As long as this file is listed in the inputs, the build will detect
+  when it has changed in any way and the action will re-run.
+
+Inputs for binary targets
+
+  Any input dependencies will be resolved before compiling any sources or
+  linking the target. Normally, all actions that a target depends on will be run
+  before any files in a target are compiled. So if you depend on generated
+  headers, you do not typically need to list them in the inputs section.
+
+  Inputs for binary targets will be treated as implicit dependencies, meaning
+  that changes in any of the inputs will force all sources in the target to be
+  recompiled. If an input only applies to a subset of source files, you may
+  want to split those into a separate target to avoid unnecessary recompiles.
+
+Example
+
+  action("myscript") {
+    script = "domything.py"
+    inputs = [ "input.data" ]
+  }
+)";
+
+const char kLdflags[] = "ldflags";
+const char kLdflags_HelpShort[] =
+    "ldflags: [string list] Flags passed to the linker.";
+const char kLdflags_Help[] =
+    R"(ldflags: Flags passed to the linker.
+
+  A list of strings.
+
+  These flags are passed on the command-line to the linker and generally
+  specify various linking options. Most targets will not need these and will
+  use "libs" and "lib_dirs" instead.
+
+  ldflags are NOT pushed to dependents, so applying ldflags to source sets or
+  static libraries will be a no-op. If you want to apply ldflags to dependent
+  targets, put them in a config and set it in the all_dependent_configs or
+  public_configs.
+)" COMMON_ORDERING_HELP;
+
+#define COMMON_LIB_INHERITANCE_HELP                                          \
+  "\n"                                                                       \
+  "  libs and lib_dirs work differently than other flags in two respects.\n" \
+  "  First, they are inherited across static library boundaries until a\n"   \
+  "  shared library or executable target is reached. Second, they are\n"     \
+  "  uniquified so each one is only passed once (the first instance of it\n" \
+  "  will be the one used).\n"
+
+#define LIBS_AND_LIB_DIRS_ORDERING_HELP                                  \
+  "\n"                                                                   \
+  "  For \"libs\" and \"lib_dirs\" only, the values propagated from\n"   \
+  "  dependencies (as described above) are applied last assuming they\n" \
+  "  are not already in the list.\n"
+
+const char kLibDirs[] = "lib_dirs";
+const char kLibDirs_HelpShort[] =
+    "lib_dirs: [directory list] Additional library directories.";
+const char kLibDirs_Help[] =
+    R"(lib_dirs: Additional library directories.
+
+  A list of directories.
+
+  Specifies additional directories passed to the linker for searching for the
+  required libraries. If an item is not an absolute path, it will be treated as
+  being relative to the current build file.
+)" COMMON_LIB_INHERITANCE_HELP COMMON_ORDERING_HELP
+        LIBS_AND_LIB_DIRS_ORDERING_HELP
+    R"(
+Example
+
+  lib_dirs = [ "/usr/lib/foo", "lib/doom_melon" ]
+)";
+
+const char kLibs[] = "libs";
+const char kLibs_HelpShort[] =
+    "libs: [string list] Additional libraries to link.";
+const char kLibs_Help[] =
+    R"(libs: Additional libraries to link.
+
+  A list of library names or library paths.
+
+  These libraries will be linked into the final binary (executable or shared
+  library) containing the current target.
+)" COMMON_LIB_INHERITANCE_HELP
+    R"(
+Types of libs
+
+  There are several different things that can be expressed in libs:
+
+  File paths
+      Values containing '/' will be treated as references to files in the
+      checkout. They will be rebased to be relative to the build directory and
+      specified in the "libs" for linker tools. This facility should be used
+      for libraries that are checked in to the version control. For libraries
+      that are generated by the build, use normal GN deps to link them.
+
+  System libraries
+      Values not containing '/' will be treated as system library names. These
+      will be passed unmodified to the linker and prefixed with the
+      "lib_switch" attribute of the linker tool. Generally you would set the
+      "lib_dirs" so the given library is found. Your BUILD.gn file should not
+      specify the switch (like "-l"): this will be encoded in the "lib_switch"
+      of the tool.
+)" COMMON_ORDERING_HELP LIBS_AND_LIB_DIRS_ORDERING_HELP
+    R"(
+Examples
+
+  On Windows:
+    libs = [ "ctl3d.lib" ]
+
+  On Linux:
+    libs = [ "ld" ]
+)";
+
+const char kMetadata[] = "metadata";
+const char kMetadata_HelpShort[] = "metadata: [scope] Metadata of this target.";
+const char kMetadata_Help[] =
+    R"(metadata: Metadata of this target.
+
+  Metadata is a collection of keys and values relating to a particular target.
+  Values must be lists, allowing for sane and predictable collection behavior.
+  Generally, these keys will include three types of lists: lists of ordinary
+  strings, lists of filenames intended to be rebased according to their
+  particular source directory, and lists of target labels intended to be used
+  as barriers to the walk. Verification of these categories occurs at walk time,
+  not creation time (since it is not clear until the walk which values are
+  intended for which purpose).
+
+Example
+
+  group("doom_melon") {
+    metadata = {
+      # These keys are not built in to GN but are interpreted when consuming
+      # metadata.
+      my_barrier = []
+      my_files = [ "a.txt", "b.txt" ]
+    }
+  }
+)";
+
+const char kOutputExtension[] = "output_extension";
+const char kOutputExtension_HelpShort[] =
+    "output_extension: [string] Value to use for the output's file extension.";
+const char kOutputExtension_Help[] =
+    R"(output_extension: Value to use for the output's file extension.
+
+  Normally the file extension for a target is based on the target type and the
+  operating system, but in rare cases you will need to override the name (for
+  example to use "libfreetype.so.6" instead of libfreetype.so on Linux).
+
+  This value should not include a leading dot. If undefined, the default
+  specified on the tool will be used. If set to the empty string, no output
+  extension will be used.
+
+  The output_extension will be used to set the "{{output_extension}}" expansion
+  which the linker tool will generally use to specify the output file name. See
+  "gn help tool".
+
+Example
+
+  shared_library("freetype") {
+    if (is_linux) {
+      # Call the output "libfreetype.so.6"
+      output_extension = "so.6"
+    }
+    ...
+  }
+
+  # On Windows, generate a "mysettings.cpl" control panel applet. Control panel
+  # applets are actually special shared libraries.
+  if (is_win) {
+    shared_library("mysettings") {
+      output_extension = "cpl"
+      ...
+    }
+  }
+)";
+
+const char kOutputDir[] = "output_dir";
+const char kOutputDir_HelpShort[] =
+    "output_dir: [directory] Directory to put output file in.";
+const char kOutputDir_Help[] =
+    R"(output_dir: [directory] Directory to put output file in.
+
+  For library and executable targets, overrides the directory for the final
+  output. This must be in the root_build_dir or a child thereof.
+
+  This should generally be in the root_out_dir or a subdirectory thereof (the
+  root_out_dir will be the same as the root_build_dir for the default
+  toolchain, and will be a subdirectory for other toolchains). Not putting the
+  output in a subdirectory of root_out_dir can result in collisions between
+  different toolchains, so you will need to take steps to ensure that your
+  target is only present in one toolchain.
+
+  Normally the toolchain specifies the output directory for libraries and
+  executables (see "gn help tool"). You will have to consult that for the
+  default location. The default location will be used if output_dir is
+  undefined or empty.
+
+Example
+
+  shared_library("doom_melon") {
+    output_dir = "$root_out_dir/plugin_libs"
+    ...
+  }
+)";
+
+const char kOutputName[] = "output_name";
+const char kOutputName_HelpShort[] =
+    "output_name: [string] Name for the output file other than the default.";
+const char kOutputName_Help[] =
+    R"(output_name: Define a name for the output file other than the default.
+
+  Normally the output name of a target will be based on the target name, so the
+  target "//foo/bar:bar_unittests" will generate an output file such as
+  "bar_unittests.exe" (using Windows as an example).
+
+  Sometimes you will want an alternate name to avoid collisions or if the
+  internal name isn't appropriate for public distribution.
+
+  The output name should have no extension or prefixes, these will be added
+  using the default system rules. For example, on Linux an output name of "foo"
+  will produce a shared library "libfoo.so". There is no way to override the
+  output prefix of a linker tool on a per- target basis. If you need more
+  flexibility, create a copy target to produce the file you want.
+
+  This variable is valid for all binary output target types.
+
+Example
+
+  static_library("doom_melon") {
+    output_name = "fluffy_bunny"
+  }
+)";
+
+const char kOutputPrefixOverride[] = "output_prefix_override";
+const char kOutputPrefixOverride_HelpShort[] =
+    "output_prefix_override: [boolean] Don't use prefix for output name.";
+const char kOutputPrefixOverride_Help[] =
+    R"(output_prefix_override: Don't use prefix for output name.
+
+  A boolean that overrides the output prefix for a target. Defaults to false.
+
+  Some systems use prefixes for the names of the final target output file. The
+  normal example is "libfoo.so" on Linux for a target named "foo".
+
+  The output prefix for a given target type is specified on the linker tool
+  (see "gn help tool"). Sometimes this prefix is undesired.
+
+  See also "gn help output_extension".
+
+Example
+
+  shared_library("doom_melon") {
+    # Normally this will produce "libdoom_melon.so" on Linux. Setting this flag
+    # will produce "doom_melon.so".
+    output_prefix_override = true
+    ...
+  }
+)";
+
+const char kPartialInfoPlist[] = "partial_info_plist";
+const char kPartialInfoPlist_HelpShort[] =
+    "partial_info_plist: [filename] Path plist from asset catalog compiler.";
+const char kPartialInfoPlist_Help[] =
+    R"(partial_info_plist: [filename] Path plist from asset catalog compiler.
+
+  Valid for create_bundle target, corresponds to the path for the partial
+  Info.plist created by the asset catalog compiler that needs to be merged
+  with the application Info.plist (usually done by the code signing script).
+
+  The file will be generated regardless of whether the asset compiler has
+  been invoked or not. See "gn help create_bundle".
+)";
+
+const char kOutputs[] = "outputs";
+const char kOutputs_HelpShort[] =
+    "outputs: [file list] Output files for actions and copy targets.";
+const char kOutputs_Help[] =
+    R"(outputs: Output files for actions and copy targets.
+
+  Outputs is valid for "copy", "action", and "action_foreach" target types and
+  indicates the resulting files. Outputs must always refer to files in the
+  build directory.
+
+  copy
+    Copy targets should have exactly one entry in the outputs list. If there is
+    exactly one source, this can be a literal file name or a source expansion.
+    If there is more than one source, this must contain a source expansion to
+    map a single input name to a single output name. See "gn help copy".
+
+  action_foreach
+    Action_foreach targets must always use source expansions to map input files
+    to output files. There can be more than one output, which means that each
+    invocation of the script will produce a set of files (presumably based on
+    the name of the input file). See "gn help action_foreach".
+
+  action
+    Action targets (excluding action_foreach) must list literal output file(s)
+    with no source expansions. See "gn help action".
+)";
+
+const char kPool[] = "pool";
+const char kPool_HelpShort[] =
+    "pool: [string] Label of the pool used by the action.";
+const char kPool_Help[] =
+    R"(pool: Label of the pool used by the action.
+
+  A fully-qualified label representing the pool that will be used for the
+  action. Pools are defined using the pool() {...} declaration.
+
+Example
+
+  action("action") {
+    pool = "//build:custom_pool"
+    ...
+  }
+)";
+
+const char kPrecompiledHeader[] = "precompiled_header";
+const char kPrecompiledHeader_HelpShort[] =
+    "precompiled_header: [string] Header file to precompile.";
+const char kPrecompiledHeader_Help[] =
+    R"(precompiled_header: [string] Header file to precompile.
+
+  Precompiled headers will be used when a target specifies this value, or a
+  config applying to this target specifies this value. In addition, the tool
+  corresponding to the source files must also specify precompiled headers (see
+  "gn help tool"). The tool will also specify what type of precompiled headers
+  to use, by setting precompiled_header_type to either "gcc" or "msvc".
+
+  The precompiled header/source variables can be specified on a target or a
+  config, but must be the same for all configs applying to a given target since
+  a target can only have one precompiled header.
+
+  If you use both C and C++ sources, the precompiled header and source file
+  will be compiled once per language. You will want to make sure to wrap C++
+  includes in __cplusplus #ifdefs so the file will compile in C mode.
+
+GCC precompiled headers
+
+  When using GCC-style precompiled headers, "precompiled_source" contains the
+  path of a .h file that is precompiled and then included by all source files
+  in targets that set "precompiled_source".
+
+  The value of "precompiled_header" is not used with GCC-style precompiled
+  headers.
+
+MSVC precompiled headers
+
+  When using MSVC-style precompiled headers, the "precompiled_header" value is
+  a string corresponding to the header. This is NOT a path to a file that GN
+  recognises, but rather the exact string that appears in quotes after
+  an #include line in source code. The compiler will match this string against
+  includes or forced includes (/FI).
+
+  MSVC also requires a source file to compile the header with. This must be
+  specified by the "precompiled_source" value. In contrast to the header value,
+  this IS a GN-style file name, and tells GN which source file to compile to
+  make the .pch file used for subsequent compiles.
+
+  For example, if the toolchain specifies MSVC headers:
+
+    toolchain("vc_x64") {
+      ...
+      tool("cxx") {
+        precompiled_header_type = "msvc"
+        ...
+
+  You might make a config like this:
+
+    config("use_precompiled_headers") {
+      precompiled_header = "build/precompile.h"
+      precompiled_source = "//build/precompile.cc"
+
+      # Either your source files should #include "build/precompile.h"
+      # first, or you can do this to force-include the header.
+      cflags = [ "/FI$precompiled_header" ]
+    }
+
+  And then define a target that uses the config:
+
+    executable("doom_melon") {
+      configs += [ ":use_precompiled_headers" ]
+      ...
+)";
+
+const char kPrecompiledHeaderType[] = "precompiled_header_type";
+const char kPrecompiledHeaderType_HelpShort[] =
+    "precompiled_header_type: [string] \"gcc\" or \"msvc\".";
+const char kPrecompiledHeaderType_Help[] =
+    R"(precompiled_header_type: [string] "gcc" or "msvc".
+
+  See "gn help precompiled_header".
+)";
+
+const char kPrecompiledSource[] = "precompiled_source";
+const char kPrecompiledSource_HelpShort[] =
+    "precompiled_source: [file name] Source file to precompile.";
+const char kPrecompiledSource_Help[] =
+    R"(precompiled_source: [file name] Source file to precompile.
+
+  The source file that goes along with the precompiled_header when using
+  "msvc"-style precompiled headers. It will be implicitly added to the sources
+  of the target. See "gn help precompiled_header".
+)";
+
+const char kProductType[] = "product_type";
+const char kProductType_HelpShort[] =
+    "product_type: [string] Product type for Xcode projects.";
+const char kProductType_Help[] =
+    R"(product_type: Product type for Xcode projects.
+
+  Correspond to the type of the product of a create_bundle target. Only
+  meaningful to Xcode (used as part of the Xcode project generation).
+
+  When generating Xcode project files, only create_bundle target with a
+  non-empty product_type will have a corresponding target in Xcode project.
+)";
+
+const char kPublic[] = "public";
+const char kPublic_HelpShort[] =
+    "public: [file list] Declare public header files for a target.";
+const char kPublic_Help[] =
+    R"(public: Declare public header files for a target.
+
+  A list of files that other targets can include. These permissions are checked
+  via the "check" command (see "gn help check").
+
+  If no public files are declared, other targets (assuming they have visibility
+  to depend on this target) can include any file in the sources list. If this
+  variable is defined on a target, dependent targets may only include files on
+  this whitelist unless that target is marked as a friend (see "gn help
+  friend").
+
+  Header file permissions are also subject to visibility. A target must be
+  visible to another target to include any files from it at all and the public
+  headers indicate which subset of those files are permitted. See "gn help
+  visibility" for more.
+
+  Public files are inherited through the dependency tree. So if there is a
+  dependency A -> B -> C, then A can include C's public headers. However, the
+  same is NOT true of visibility, so unless A is in C's visibility list, the
+  include will be rejected.
+
+  GN only knows about files declared in the "sources" and "public" sections of
+  targets. If a file is included that is not known to the build, it will be
+  allowed.
+
+  It is common for test targets to need to include private headers for their
+  associated code. In this case, list the test target in the "friend" list of
+  the target that owns the private header to allow the inclusion. See
+  "gn help friend" for more.
+
+  When a binary target has no explicit or implicit public headers (a "public"
+  list is defined but is empty), GN assumes that the target can not propagate
+  any compile-time dependencies up the dependency tree. In this case, the build
+  can be parallelized more efficiently.
+  Say there are dependencies:
+    A (shared library) -> B (shared library) -> C (action).
+  Normally C must complete before any source files in A can compile (because
+  there might be generated includes). But when B explicitly declares no public
+  headers, C can execute in parallel with A's compile steps. C must still be
+  complete before any dependents link.
+
+Examples
+
+  These exact files are public:
+    public = [ "foo.h", "bar.h" ]
+
+  No files are public (no targets may include headers from this one):
+    # This allows starting compilation in dependent targets earlier.
+    public = []
+)";
+
+const char kPublicConfigs[] = "public_configs";
+const char kPublicConfigs_HelpShort[] =
+    "public_configs: [label list] Configs applied to dependents.";
+const char kPublicConfigs_Help[] =
+    R"(public_configs: Configs to be applied on dependents.
+
+  A list of config labels.
+
+  Targets directly depending on this one will have the configs listed in this
+  variable added to them. These configs will also apply to the current target.
+  Generally, public configs are used to apply defines and include directories
+  necessary to compile this target's header files.
+
+  See also "gn help all_dependent_configs".
+
+Propagation of public configs
+
+  Public configs are applied to all targets that depend directly on this one.
+  These dependent targets can further push this target's public configs
+  higher in the dependency tree by depending on it via public_deps (see "gn
+  help public_deps").
+
+    static_library("toplevel") {
+      # This target will get "my_config" applied to it. However, since this
+      # target uses "deps" and not "public_deps", targets that depend on this
+      # one won't get it.
+      deps = [ ":intermediate" ]
+    }
+
+    static_library("intermediate") {
+      # Depending on "lower" in any way will apply "my_config" to this target.
+      # Additionall, since this target depends on "lower" via public_deps,
+      # targets that depend on this one will also get "my_config".
+      public_deps = [ ":lower" ]
+    }
+
+    static_library("lower") {
+      # This will get applied to all targets that depend on this one.
+      public_configs = [ ":my_config" ]
+    }
+
+  Public config propagation happens in a second phase once a target and all of
+  its dependencies have been resolved. Therefore, a target will not see these
+  force-added configs in their "configs" variable while the script is running,
+  and they can not be removed. As a result, this capability should generally
+  only be used to add defines and include directories rather than setting
+  complicated flags that some targets may not want.
+
+  Public configs may or may not be propagated across toolchain boundaries
+  depending on the value of the propagates_configs flag (see "gn help
+  toolchain") on the toolchain of the target declaring the public_config.
+
+Avoiding applying public configs to this target
+
+  If you want the config to apply to targets that depend on this one, but NOT
+  this one, define an extra layer of indirection using a group:
+
+    # External targets depend on this group.
+    group("my_target") {
+      # Config to apply to all targets that depend on this one.
+      public_configs = [ ":external_settings" ]
+      deps = [ ":internal_target" ]
+    }
+
+    # Internal target to actually compile the sources.
+    static_library("internal_target") {
+      # Force all external targets to depend on the group instead of directly
+      # on this so the "external_settings" config will get applied.
+      visibility = [ ":my_target" ]
+      ...
+    }
+
+)" COMMON_ORDERING_HELP;
+
+const char kPublicDeps[] = "public_deps";
+const char kPublicDeps_HelpShort[] =
+    "public_deps: [label list] Declare public dependencies.";
+const char kPublicDeps_Help[] =
+    R"(public_deps: Declare public dependencies.
+
+  Public dependencies are like private dependencies (see "gn help deps") but
+  additionally express that the current target exposes the listed deps as part
+  of its public API.
+
+  This has several ramifications:
+
+    - public_configs that are part of the dependency are forwarded to direct
+      dependents.
+
+    - Public headers in the dependency are usable by dependents (includes do
+      not require a direct dependency or visibility).
+
+    - If the current target is a shared library, other shared libraries that it
+      publicly depends on (directly or indirectly) are propagated up the
+      dependency tree to dependents for linking.
+
+  See also "gn help public_configs".
+
+Discussion
+
+  Say you have three targets: A -> B -> C. C's visibility may allow B to depend
+  on it but not A. Normally, this would prevent A from including any headers
+  from C, and C's public_configs would apply only to B.
+
+  If B lists C in its public_deps instead of regular deps, A will now inherit
+  C's public_configs and the ability to include C's public headers.
+
+  Generally if you are writing a target B and you include C's headers as part
+  of B's public headers, or targets depending on B should consider B and C to
+  be part of a unit, you should use public_deps instead of deps.
+
+Example
+
+  # This target can include files from "c" but not from
+  # "super_secret_implementation_details".
+  executable("a") {
+    deps = [ ":b" ]
+  }
+
+  shared_library("b") {
+    deps = [ ":super_secret_implementation_details" ]
+    public_deps = [ ":c" ]
+  }
+)";
+
+const char kRebase[] = "rebase";
+const char kRebase_HelpShort[] =
+    "rebase: [boolean] Rebase collected metadata as files.";
+const char kRebase_Help[] =
+    R"(rebase: Rebase collected metadata as files.
+
+  A boolean that triggers a rebase of collected metadata strings based on their
+  declared file. Defaults to false.
+
+  Metadata generally declares files as strings relative to the local build file.
+  However, this data is often used in other contexts, and so setting this flag
+  will force the metadata collection to be rebased according to the local build
+  file's location and thus allow the filename to be used anywhere.
+
+  Setting this flag will raise an error if any target's specified metadata is
+  not a string value.
+
+  See also "gn help generated_file".
+)";
+
+const char kResponseFileContents[] = "response_file_contents";
+const char kResponseFileContents_HelpShort[] =
+    "response_file_contents: [string list] Contents of .rsp file for actions.";
+const char kResponseFileContents_Help[] =
+    R"*(response_file_contents: Contents of a response file for actions.
+
+  Sometimes the arguments passed to a script can be too long for the system's
+  command-line capabilities. This is especially the case on Windows where the
+  maximum command-line length is less than 8K. A response file allows you to
+  pass an unlimited amount of data to a script in a temporary file for an
+  action or action_foreach target.
+
+  If the response_file_contents variable is defined and non-empty, the list
+  will be treated as script args (including possibly substitution patterns)
+  that will be written to a temporary file at build time. The name of the
+  temporary file will be substituted for "{{response_file_name}}" in the script
+  args.
+
+  The response file contents will always be quoted and escaped according to
+  Unix shell rules. To parse the response file, the Python script should use
+  "shlex.split(file_contents)".
+
+Example
+
+  action("process_lots_of_files") {
+    script = "process.py",
+    inputs = [ ... huge list of files ... ]
+
+    # Write all the inputs to a response file for the script. Also,
+    # make the paths relative to the script working directory.
+    response_file_contents = rebase_path(inputs, root_build_dir)
+
+    # The script expects the name of the response file in --file-list.
+    args = [
+      "--enable-foo",
+      "--file-list={{response_file_name}}",
+    ]
+  }
+)*";
+
+const char kScript[] = "script";
+const char kScript_HelpShort[] = "script: [file name] Script file for actions.";
+const char kScript_Help[] =
+    R"(script: Script file for actions.
+
+  An absolute or buildfile-relative file name of a Python script to run for a
+  action and action_foreach targets (see "gn help action" and "gn help
+  action_foreach").
+)";
+
+const char kSources[] = "sources";
+const char kSources_HelpShort[] =
+    "sources: [file list] Source files for a target.";
+const char kSources_Help[] =
+    R"(sources: Source files for a target
+
+  A list of files. Non-absolute paths will be resolved relative to the current
+  build file.
+
+Sources for binary targets
+
+  For binary targets (source sets, executables, and libraries), the known file
+  types will be compiled with the associated tools. Unknown file types and
+  headers will be skipped. However, you should still list all C/C+ header files
+  so GN knows about the existence of those files for the purposes of include
+  checking.
+
+  As a special case, a file ending in ".def" will be treated as a Windows
+  module definition file. It will be appended to the link line with a
+  preceding "/DEF:" string. There must be at most one .def file in a target
+  and they do not cross dependency boundaries (so specifying a .def file in a
+  static library or source set will have no effect on the executable or shared
+  library they're linked into).
+
+  For Rust targets that do not specify a crate_root, then the crate_root will
+  look for a lib.rs file (or main.rs for executable) or a single file in
+  sources, if sources contains only one file.
+
+Sources for non-binary targets
+
+  action_foreach
+    The sources are the set of files that the script will be executed over. The
+    script will run once per file.
+
+  action
+    The sources will be treated the same as inputs. See "gn help inputs" for
+    more information and usage advice.
+
+  copy
+    The source are the source files to copy.
+)";
+
+const char kSwiftflags[] = "swiftflags";
+const char kSwiftflags_HelpShort[] =
+    "swiftflags: [string list] Flags passed to the swift compiler.";
+const char* kSwiftflags_Help =
+    R"(swiftflags: Flags passed to the swift compiler.
+
+  A list of strings.
+
+  "swiftflags" are passed to any invocation of a tool that takes an .swift
+  file as input.
+)" COMMON_ORDERING_HELP;
+
+const char kXcodeTestApplicationName[] = "xcode_test_application_name";
+const char kXcodeTestApplicationName_HelpShort[] =
+    "xcode_test_application_name: [string] Name for Xcode test target.";
+const char kXcodeTestApplicationName_Help[] =
+    R"(xcode_test_application_name: Name for Xcode test target.
+
+  Each unit and ui test target must have a test application target, and this
+  value is used to specify the relationship. Only meaningful to Xcode (used as
+  part of the Xcode project generation).
+
+  See "gn help create_bundle" for more information.
+
+Example
+
+  create_bundle("chrome_xctest") {
+    test_application_name = "chrome"
+    ...
+  }
+)";
+
+const char kTestonly[] = "testonly";
+const char kTestonly_HelpShort[] =
+    "testonly: [boolean] Declares a target must only be used for testing.";
+const char kTestonly_Help[] =
+    R"(testonly: Declares a target must only be used for testing.
+
+  Boolean. Defaults to false.
+
+  When a target is marked "testonly = true", it must only be depended on by
+  other test-only targets. Otherwise, GN will issue an error that the
+  depenedency is not allowed.
+
+  This feature is intended to prevent accidentally shipping test code in a
+  final product.
+
+Example
+
+  source_set("test_support") {
+    testonly = true
+    ...
+  }
+)";
+
+const char kVisibility[] = "visibility";
+const char kVisibility_HelpShort[] =
+    "visibility: [label list] A list of labels that can depend on a target.";
+const char kVisibility_Help[] =
+    R"(visibility: A list of labels that can depend on a target.
+
+  A list of labels and label patterns that define which targets can depend on
+  the current one. These permissions are checked via the "check" command (see
+  "gn help check").
+
+  If visibility is not defined, it defaults to public ("*").
+
+  If visibility is defined, only the targets with labels that match it can
+  depend on the current target. The empty list means no targets can depend on
+  the current target.
+
+  Tip: Often you will want the same visibility for all targets in a BUILD file.
+  In this case you can just put the definition at the top, outside of any
+  target, and the targets will inherit that scope and see the definition.
+
+Patterns
+
+  See "gn help label_pattern" for more details on what types of patterns are
+  supported. If a toolchain is specified, only targets in that toolchain will
+  be matched. If a toolchain is not specified on a pattern, targets in all
+  toolchains will be matched.
+
+Examples
+
+  Only targets in the current buildfile ("private"):
+    visibility = [ ":*" ]
+
+  No targets (used for targets that should be leaf nodes):
+    visibility = []
+
+  Any target ("public", the default):
+    visibility = [ "*" ]
+
+  All targets in the current directory and any subdirectory:
+    visibility = [ "./*" ]
+
+  Any target in "//bar/BUILD.gn":
+    visibility = [ "//bar:*" ]
+
+  Any target in "//bar/" or any subdirectory thereof:
+    visibility = [ "//bar/*" ]
+
+  Just these specific targets:
+    visibility = [ ":mything", "//foo:something_else" ]
+
+  Any target in the current directory and any subdirectory thereof, plus
+  any targets in "//bar/" and any subdirectory thereof.
+    visibility = [ "./*", "//bar/*" ]
+)";
+
+const char kWalkKeys[] = "walk_keys";
+const char kWalkKeys_HelpShort[] =
+    "walk_keys: [string list] Key(s) for managing the metadata collection "
+    "walk.";
+const char kWalkKeys_Help[] =
+    R"(walk_keys: Key(s) for managing the metadata collection walk.
+
+  Defaults to [""].
+
+  These keys are used to control the next step in a collection walk, acting as
+  barriers. If a specified key is defined in a target's metadata, the walk will
+  use the targets listed in that value to determine which targets are walked.
+
+  If no walk_keys are specified for a generated_file target (i.e. "[""]"), the
+  walk will touch all deps and data_deps of the specified target recursively.
+
+  See "gn help generated_file".
+)";
+
+const char kWeakFrameworks[] = "weak_frameworks";
+const char kWeakFrameworks_HelpShort[] =
+    "weak_frameworks: [name list] Name of frameworks that must be weak linked.";
+const char kWeakFrameworks_Help[] =
+    R"(weak_frameworks: [name list] Name of frameworks that must be weak linked.
+
+  A list of framework names.
+
+  The frameworks named in that list will be weak linked with any dynamic link
+  type target. Weak linking instructs the dynamic loader to attempt to load
+  the framework, but if it is not able to do so, it leaves any imported symbols
+  unresolved. This is typically used when a framework is present in a new
+  version of an SDK but not on older versions of the OS that the software runs
+  on.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
+)";
+
+const char kWriteValueContents[] = "contents";
+const char kWriteValueContents_HelpShort[] =
+    "contents: Contents to write to file.";
+const char kWriteValueContents_Help[] =
+    R"(contents: Contents to write to file.
+
+  The contents of the file for a generated_file target.
+  See "gn help generated_file".
+)";
+
+const char kWriteOutputConversion[] = "output_conversion";
+const char kWriteOutputConversion_HelpShort[] =
+    "output_conversion: Data format for generated_file targets.";
+const char kWriteOutputConversion_Help[] =
+    R"(output_conversion: Data format for generated_file targets.
+
+  Controls how the "contents" of a generated_file target is formatted.
+  See "gn help io_conversion".
+)";
+
+const char kWriteRuntimeDeps[] = "write_runtime_deps";
+const char kWriteRuntimeDeps_HelpShort[] =
+    "write_runtime_deps: Writes the target's runtime_deps to the given path.";
+const char kWriteRuntimeDeps_Help[] =
+    R"(write_runtime_deps: Writes the target's runtime_deps to the given path.
+
+  Does not synchronously write the file, but rather schedules it to be written
+  at the end of generation.
+
+  If the file exists and the contents are identical to that being written, the
+  file will not be updated. This will prevent unnecessary rebuilds of targets
+  that depend on this file.
+
+  Path must be within the output directory.
+
+  See "gn help runtime_deps" for how the runtime dependencies are computed.
+
+  The format of this file will list one file per line with no escaping. The
+  files will be relative to the root_build_dir. The first line of the file will
+  be the main output file of the target itself. The file contents will be the
+  same as requesting the runtime deps be written on the command line (see "gn
+  help --runtime-deps-list-file").
+)";
+
+const char kXcodeExtraAttributes[] = "xcode_extra_attributes";
+const char kXcodeExtraAttributes_HelpShort[] =
+    "xcode_extra_attributes: [scope] Extra attributes for Xcode projects.";
+const char kXcodeExtraAttributes_Help[] =
+    R"(xcode_extra_attributes: [scope] Extra attributes for Xcode projects.
+
+  The value defined in this scope will be copied to the EXTRA_ATTRIBUTES
+  property of the generated Xcode project. They are only meaningful when
+  generating with --ide=xcode.
+
+  See "gn help create_bundle" for more information.
+)";
+
+// -----------------------------------------------------------------------------
+
+VariableInfo::VariableInfo() : help_short(""), help("") {}
+
+VariableInfo::VariableInfo(const char* in_help_short, const char* in_help)
+    : help_short(in_help_short), help(in_help) {}
+
+#define INSERT_VARIABLE(var) \
+  info_map[k##var] = VariableInfo(k##var##_HelpShort, k##var##_Help);
+
+const VariableInfoMap& GetBuiltinVariables() {
+  static VariableInfoMap info_map;
+  if (info_map.empty()) {
+    INSERT_VARIABLE(CurrentCpu)
+    INSERT_VARIABLE(CurrentOs)
+    INSERT_VARIABLE(CurrentToolchain)
+    INSERT_VARIABLE(DefaultToolchain)
+    INSERT_VARIABLE(GnVersion)
+    INSERT_VARIABLE(HostCpu)
+    INSERT_VARIABLE(HostOs)
+    INSERT_VARIABLE(Invoker)
+    INSERT_VARIABLE(PythonPath)
+    INSERT_VARIABLE(RootBuildDir)
+    INSERT_VARIABLE(RootGenDir)
+    INSERT_VARIABLE(RootOutDir)
+    INSERT_VARIABLE(TargetCpu)
+    INSERT_VARIABLE(TargetGenDir)
+    INSERT_VARIABLE(TargetName)
+    INSERT_VARIABLE(TargetOs)
+    INSERT_VARIABLE(TargetOutDir)
+  }
+  return info_map;
+}
+
+const VariableInfoMap& GetTargetVariables() {
+  static VariableInfoMap info_map;
+  if (info_map.empty()) {
+    INSERT_VARIABLE(AllDependentConfigs)
+    INSERT_VARIABLE(AllowCircularIncludesFrom)
+    INSERT_VARIABLE(Arflags)
+    INSERT_VARIABLE(Args)
+    INSERT_VARIABLE(Asmflags)
+    INSERT_VARIABLE(AssertNoDeps)
+    INSERT_VARIABLE(BundleRootDir)
+    INSERT_VARIABLE(BundleContentsDir)
+    INSERT_VARIABLE(BundleResourcesDir)
+    INSERT_VARIABLE(BundleDepsFilter)
+    INSERT_VARIABLE(BundleExecutableDir)
+    INSERT_VARIABLE(XcassetCompilerFlags)
+    INSERT_VARIABLE(Cflags)
+    INSERT_VARIABLE(CflagsC)
+    INSERT_VARIABLE(CflagsCC)
+    INSERT_VARIABLE(CflagsObjC)
+    INSERT_VARIABLE(CflagsObjCC)
+    INSERT_VARIABLE(CheckIncludes)
+    INSERT_VARIABLE(CodeSigningArgs)
+    INSERT_VARIABLE(CodeSigningScript)
+    INSERT_VARIABLE(CodeSigningSources)
+    INSERT_VARIABLE(CodeSigningOutputs)
+    INSERT_VARIABLE(CompleteStaticLib)
+    INSERT_VARIABLE(Configs)
+    INSERT_VARIABLE(Data)
+    INSERT_VARIABLE(DataDeps)
+    INSERT_VARIABLE(DataKeys)
+    INSERT_VARIABLE(Defines)
+    INSERT_VARIABLE(Depfile)
+    INSERT_VARIABLE(Deps)
+    INSERT_VARIABLE(Externs)
+    INSERT_VARIABLE(Friend)
+    INSERT_VARIABLE(FrameworkDirs)
+    INSERT_VARIABLE(Frameworks)
+    INSERT_VARIABLE(IncludeDirs)
+    INSERT_VARIABLE(Inputs)
+    INSERT_VARIABLE(Ldflags)
+    INSERT_VARIABLE(Libs)
+    INSERT_VARIABLE(LibDirs)
+    INSERT_VARIABLE(Metadata)
+    INSERT_VARIABLE(OutputDir)
+    INSERT_VARIABLE(OutputExtension)
+    INSERT_VARIABLE(OutputName)
+    INSERT_VARIABLE(OutputPrefixOverride)
+    INSERT_VARIABLE(Outputs)
+    INSERT_VARIABLE(PartialInfoPlist)
+    INSERT_VARIABLE(Pool)
+    INSERT_VARIABLE(PrecompiledHeader)
+    INSERT_VARIABLE(PrecompiledHeaderType)
+    INSERT_VARIABLE(PrecompiledSource)
+    INSERT_VARIABLE(ProductType)
+    INSERT_VARIABLE(Public)
+    INSERT_VARIABLE(PublicConfigs)
+    INSERT_VARIABLE(PublicDeps)
+    INSERT_VARIABLE(Rebase)
+    INSERT_VARIABLE(ResponseFileContents)
+    INSERT_VARIABLE(Script)
+    INSERT_VARIABLE(Sources)
+    INSERT_VARIABLE(Swiftflags)
+    INSERT_VARIABLE(XcodeTestApplicationName)
+    INSERT_VARIABLE(Testonly)
+    INSERT_VARIABLE(Visibility)
+    INSERT_VARIABLE(WalkKeys)
+    INSERT_VARIABLE(WeakFrameworks)
+    INSERT_VARIABLE(WriteOutputConversion)
+    INSERT_VARIABLE(WriteValueContents)
+    INSERT_VARIABLE(WriteRuntimeDeps)
+    INSERT_VARIABLE(XcodeExtraAttributes)
+    InsertRustVariables(&info_map);
+    InsertSwiftVariables(&info_map);
+  }
+  return info_map;
+}
+
+#undef INSERT_VARIABLE
+
+}  // namespace variables
diff --git a/src/gn/variables.h b/src/gn/variables.h
new file mode 100644 (file)
index 0000000..f5794d1
--- /dev/null
@@ -0,0 +1,379 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VARIABLES_H_
+#define TOOLS_GN_VARIABLES_H_
+
+#include <map>
+#include <string_view>
+
+namespace variables {
+
+// Builtin vars ----------------------------------------------------------------
+
+extern const char kHostCpu[];
+extern const char kHostCpu_HelpShort[];
+extern const char kHostCpu_Help[];
+
+extern const char kHostOs[];
+extern const char kHostOs_HelpShort[];
+extern const char kHostOs_Help[];
+
+extern const char kCurrentCpu[];
+extern const char kCurrentCpu_HelpShort[];
+extern const char kCurrentCpu_Help[];
+
+extern const char kCurrentOs[];
+extern const char kCurrentOs_HelpShort[];
+extern const char kCurrentOs_Help[];
+
+extern const char kCurrentToolchain[];
+extern const char kCurrentToolchain_HelpShort[];
+extern const char kCurrentToolchain_Help[];
+
+extern const char kDefaultToolchain[];
+extern const char kDefaultToolchain_HelpShort[];
+extern const char kDefaultToolchain_Help[];
+
+extern const char kGnVersion[];
+extern const char kGnVersion_HelpShort[];
+extern const char kGnVersion_Help[];
+
+extern const char kInvoker[];
+extern const char kInvoker_HelpShort[];
+extern const char kInvoker_Help[];
+
+extern const char kPythonPath[];
+extern const char kPythonPath_HelpShort[];
+extern const char kPythonPath_Help[];
+
+extern const char kRootBuildDir[];
+extern const char kRootBuildDir_HelpShort[];
+extern const char kRootBuildDir_Help[];
+
+extern const char kRootGenDir[];
+extern const char kRootGenDir_HelpShort[];
+extern const char kRootGenDir_Help[];
+
+extern const char kRootOutDir[];
+extern const char kRootOutDir_HelpShort[];
+extern const char kRootOutDir_Help[];
+
+extern const char kTargetCpu[];
+extern const char kTargetCpu_HelpShort[];
+extern const char kTargetCpu_Help[];
+
+extern const char kTargetName[];
+extern const char kTargetName_HelpShort[];
+extern const char kTargetName_Help[];
+
+extern const char kTargetOs[];
+extern const char kTargetOs_HelpShort[];
+extern const char kTargetOs_Help[];
+
+extern const char kTargetGenDir[];
+extern const char kTargetGenDir_HelpShort[];
+extern const char kTargetGenDir_Help[];
+
+extern const char kTargetOutDir[];
+extern const char kTargetOutDir_HelpShort[];
+extern const char kTargetOutDir_Help[];
+
+// Target vars -----------------------------------------------------------------
+
+extern const char kAllDependentConfigs[];
+extern const char kAllDependentConfigs_HelpShort[];
+extern const char kAllDependentConfigs_Help[];
+
+extern const char kAllowCircularIncludesFrom[];
+extern const char kAllowCircularIncludesFrom_HelpShort[];
+extern const char kAllowCircularIncludesFrom_Help[];
+
+extern const char kArflags[];
+extern const char kArflags_HelpShort[];
+extern const char kArflags_Help[];
+
+extern const char kArgs[];
+extern const char kArgs_HelpShort[];
+extern const char kArgs_Help[];
+
+extern const char kAsmflags[];
+extern const char kAsmflags_HelpShort[];
+extern const char* kAsmflags_Help;
+
+extern const char kAssertNoDeps[];
+extern const char kAssertNoDeps_HelpShort[];
+extern const char kAssertNoDeps_Help[];
+
+extern const char kBundleRootDir[];
+extern const char kBundleRootDir_HelpShort[];
+extern const char kBundleRootDir_Help[];
+
+extern const char kBundleContentsDir[];
+extern const char kBundleContentsDir_HelpShort[];
+extern const char kBundleContentsDir_Help[];
+
+extern const char kBundleResourcesDir[];
+extern const char kBundleResourcesDir_HelpShort[];
+extern const char kBundleResourcesDir_Help[];
+
+extern const char kBundleDepsFilter[];
+extern const char kBundleDepsFilter_HelpShort[];
+extern const char kBundleDepsFilter_Help[];
+
+extern const char kBundleExecutableDir[];
+extern const char kBundleExecutableDir_HelpShort[];
+extern const char kBundleExecutableDir_Help[];
+
+extern const char kXcassetCompilerFlags[];
+extern const char kXcassetCompilerFlags_HelpShort[];
+extern const char kXcassetCompilerFlags_Help[];
+
+extern const char kCflags[];
+extern const char kCflags_HelpShort[];
+extern const char* kCflags_Help;
+
+extern const char kCflagsC[];
+extern const char kCflagsC_HelpShort[];
+extern const char* kCflagsC_Help;
+
+extern const char kCflagsCC[];
+extern const char kCflagsCC_HelpShort[];
+extern const char* kCflagsCC_Help;
+
+extern const char kCflagsObjC[];
+extern const char kCflagsObjC_HelpShort[];
+extern const char* kCflagsObjC_Help;
+
+extern const char kCflagsObjCC[];
+extern const char kCflagsObjCC_HelpShort[];
+extern const char* kCflagsObjCC_Help;
+
+extern const char kCheckIncludes[];
+extern const char kCheckIncludes_HelpShort[];
+extern const char kCheckIncludes_Help[];
+
+extern const char kCodeSigningArgs[];
+extern const char kCodeSigningArgs_HelpShort[];
+extern const char kCodeSigningArgs_Help[];
+
+extern const char kCodeSigningScript[];
+extern const char kCodeSigningScript_HelpShort[];
+extern const char kCodeSigningScript_Help[];
+
+extern const char kCodeSigningSources[];
+extern const char kCodeSigningSources_HelpShort[];
+extern const char kCodeSigningSources_Help[];
+
+extern const char kCodeSigningOutputs[];
+extern const char kCodeSigningOutputs_HelpShort[];
+extern const char kCodeSigningOutputs_Help[];
+
+extern const char kCompleteStaticLib[];
+extern const char kCompleteStaticLib_HelpShort[];
+extern const char kCompleteStaticLib_Help[];
+
+extern const char kConfigs[];
+extern const char kConfigs_HelpShort[];
+extern const char kConfigs_Help[];
+
+extern const char kData[];
+extern const char kData_HelpShort[];
+extern const char kData_Help[];
+
+extern const char kDataDeps[];
+extern const char kDataDeps_HelpShort[];
+extern const char kDataDeps_Help[];
+
+extern const char kDataKeys[];
+extern const char kDataKeys_HelpShort[];
+extern const char kDataKeys_Help[];
+
+extern const char kDefines[];
+extern const char kDefines_HelpShort[];
+extern const char kDefines_Help[];
+
+extern const char kDepfile[];
+extern const char kDepfile_HelpShort[];
+extern const char kDepfile_Help[];
+
+extern const char kDeps[];
+extern const char kDeps_HelpShort[];
+extern const char kDeps_Help[];
+
+extern const char kExterns[];
+extern const char kExterns_HelpShort[];
+extern const char kExterns_Help[];
+
+extern const char kFriend[];
+extern const char kFriend_HelpShort[];
+extern const char kFriend_Help[];
+
+extern const char kFrameworkDirs[];
+extern const char kFrameworkDirs_HelpShort[];
+extern const char kFrameworkDirs_Help[];
+
+extern const char kFrameworks[];
+extern const char kFrameworks_HelpShort[];
+extern const char kFrameworks_Help[];
+
+extern const char kIncludeDirs[];
+extern const char kIncludeDirs_HelpShort[];
+extern const char kIncludeDirs_Help[];
+
+extern const char kInputs[];
+extern const char kInputs_HelpShort[];
+extern const char kInputs_Help[];
+
+extern const char kLdflags[];
+extern const char kLdflags_HelpShort[];
+extern const char kLdflags_Help[];
+
+extern const char kLibDirs[];
+extern const char kLibDirs_HelpShort[];
+extern const char kLibDirs_Help[];
+
+extern const char kLibs[];
+extern const char kLibs_HelpShort[];
+extern const char kLibs_Help[];
+
+extern const char kMetadata[];
+extern const char kMetadata_HelpShort[];
+extern const char kMetadata_Help[];
+
+extern const char kOutputDir[];
+extern const char kOutputDir_HelpShort[];
+extern const char kOutputDir_Help[];
+
+extern const char kOutputExtension[];
+extern const char kOutputExtension_HelpShort[];
+extern const char kOutputExtension_Help[];
+
+extern const char kOutputName[];
+extern const char kOutputName_HelpShort[];
+extern const char kOutputName_Help[];
+
+extern const char kOutputPrefixOverride[];
+extern const char kOutputPrefixOverride_HelpShort[];
+extern const char kOutputPrefixOverride_Help[];
+
+extern const char kOutputs[];
+extern const char kOutputs_HelpShort[];
+extern const char kOutputs_Help[];
+
+extern const char kPartialInfoPlist[];
+extern const char kPartialInfoPlist_HelpShort[];
+extern const char kPartialInfoPlist_Help[];
+
+extern const char kPool[];
+extern const char kPool_HelpShort[];
+extern const char kPool_Help[];
+
+extern const char kPrecompiledHeader[];
+extern const char kPrecompiledHeader_HelpShort[];
+extern const char kPrecompiledHeader_Help[];
+
+extern const char kPrecompiledHeaderType[];
+extern const char kPrecompiledHeaderType_HelpShort[];
+extern const char kPrecompiledHeaderType_Help[];
+
+extern const char kPrecompiledSource[];
+extern const char kPrecompiledSource_HelpShort[];
+extern const char kPrecompiledSource_Help[];
+
+extern const char kProductType[];
+extern const char kProductType_HelpShort[];
+extern const char kProductType_Help[];
+
+extern const char kPublic[];
+extern const char kPublic_HelpShort[];
+extern const char kPublic_Help[];
+
+extern const char kPublicConfigs[];
+extern const char kPublicConfigs_HelpShort[];
+extern const char kPublicConfigs_Help[];
+
+extern const char kPublicDeps[];
+extern const char kPublicDeps_HelpShort[];
+extern const char kPublicDeps_Help[];
+
+extern const char kRebase[];
+extern const char kRebase_HelpShort[];
+extern const char kRebase_Help[];
+
+extern const char kResponseFileContents[];
+extern const char kResponseFileContents_HelpShort[];
+extern const char kResponseFileContents_Help[];
+
+extern const char kScript[];
+extern const char kScript_HelpShort[];
+extern const char kScript_Help[];
+
+extern const char kSources[];
+extern const char kSources_HelpShort[];
+extern const char kSources_Help[];
+
+extern const char kSwiftflags[];
+extern const char kSwiftflags_HelpShort[];
+extern const char* kSwiftflags_Help;
+
+extern const char kXcodeTestApplicationName[];
+extern const char kXcodeTestApplicationName_HelpShort[];
+extern const char kXcodeTestApplicationName_Help[];
+
+extern const char kTestonly[];
+extern const char kTestonly_HelpShort[];
+extern const char kTestonly_Help[];
+
+extern const char kVisibility[];
+extern const char kVisibility_HelpShort[];
+extern const char kVisibility_Help[];
+
+extern const char kWalkKeys[];
+extern const char kWalkKeys_HelpShort[];
+extern const char kWalkKeys_Help[];
+
+extern const char kWeakFrameworks[];
+extern const char kWeakFrameworks_HelpShort[];
+extern const char kWeakFrameworks_Help[];
+
+extern const char kWriteValueContents[];
+extern const char kWriteValueContents_HelpShort[];
+extern const char kWriteValueContents_Help[];
+
+extern const char kWriteOutputConversion[];
+extern const char kWriteOutputConversion_HelpShort[];
+extern const char kWriteOutputConversion_Help[];
+
+extern const char kWriteRuntimeDeps[];
+extern const char kWriteRuntimeDeps_HelpShort[];
+extern const char kWriteRuntimeDeps_Help[];
+
+extern const char kXcodeExtraAttributes[];
+extern const char kXcodeExtraAttributes_HelpShort[];
+extern const char kXcodeExtraAttributes_Help[];
+
+// -----------------------------------------------------------------------------
+
+struct VariableInfo {
+  VariableInfo();
+  VariableInfo(const char* in_help_short, const char* in_help);
+
+  const char* help_short;
+  const char* help;
+};
+
+using VariableInfoMap = std::map<std::string_view, VariableInfo>;
+
+// Returns the built-in readonly variables.
+// Note: this is used only for help so this getter is not threadsafe.
+const VariableInfoMap& GetBuiltinVariables();
+
+// Returns the variables used by target generators.
+// Note: this is used only for help so this getter is not threadsafe.
+const VariableInfoMap& GetTargetVariables();
+
+}  // namespace variables
+
+#endif  // TOOLS_GN_VARIABLES_H_
diff --git a/src/gn/vector_utils.h b/src/gn/vector_utils.h
new file mode 100644 (file)
index 0000000..8366dda
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VECTOR_UTILS_H_
+#define TOOLS_GN_VECTOR_UTILS_H_
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+// A VectorSetSorter is a convenience class used to efficiently sort and
+// de-duplicate one or more sets of items of type T, then iterate over the
+// result, or get it as a simple vector. Usage is the following:
+//
+// For performance reasons, this implementation only stores pointers to the
+// input items in order to minimize memory usage. Callers should ensure the
+// items added to this sorter do not change until the instance is destroyed.
+//
+//    1) Create instance, passing an optional initial capacity.
+//
+//    2) Add items using one of the Add() methods, as many times as
+//       necessary. Note that this records only pointers to said items
+//       so their content should not change until the instance is destroyed.
+//
+//    3) Call IteratorOver() to iterate over all sorted and de-duplicated
+//       items.
+//
+//    4) Alternatively, call AsVector() to return a new vector that contains
+//       copies of the original sorted / deduplicated items.
+//
+template <typename T>
+class VectorSetSorter {
+ public:
+  // Constructor. |initial_capacity| might be provided to minimize the number
+  // of allocations performed by this instance, if the maximum number of
+  // input items is known in advance.
+  VectorSetSorter(size_t initial_capacity = 0) {
+    ptrs_.reserve(initial_capacity);
+  }
+
+  // Add one single item to the sorter.
+  void Add(const T& item) {
+    ptrs_.push_back(&item);
+    sorted_ = false;
+  }
+
+  // Add one range of items to the sorter.
+  void Add(typename std::vector<T>::const_iterator begin,
+           typename std::vector<T>::const_iterator end) {
+    for (; begin != end; ++begin)
+      ptrs_.push_back(&(*begin));
+    sorted_ = false;
+  }
+
+  // Add one range of items to the sorter.
+  void Add(const T* start, const T* end) {
+    for (; start != end; ++start)
+      ptrs_.push_back(start);
+    sorted_ = false;
+  }
+
+  // Iterate over all sorted items, removing duplicates from the loop.
+  // |item_callback| is a callable that will be invoked for each item in the
+  // result.
+  template <typename ITEM_CALLBACK>
+  void IterateOver(ITEM_CALLBACK item_callback) {
+    if (!sorted_) {
+      Sort();
+    }
+    const T* prev_item = nullptr;
+    for (const T* item : ptrs_) {
+      if (!prev_item || *prev_item != *item) {
+        item_callback(*item);
+        prev_item = item;
+      }
+    }
+  }
+
+  // Return the sorted and de-duplicated resulting set as a vector of items.
+  // Note that this copies the input items.
+  std::vector<T> AsVector() {
+    std::vector<T> result;
+    result.reserve(ptrs_.size());
+    IterateOver([&result](const T& item) { result.push_back(item); });
+    return result;
+  }
+
+ private:
+  // Sort all items previously added to this instance.
+  // Must be called after adding all desired items, and before
+  // calling IterateOver() or AsVector().
+  void Sort() {
+    std::sort(ptrs_.begin(), ptrs_.end(),
+              [](const T* a, const T* b) { return *a < *b; });
+    sorted_ = true;
+  }
+
+  std::vector<const T*> ptrs_;
+  bool sorted_ = false;
+};
+
+#endif  // TOOLS_GN_VECTOR_UTILS_H_
diff --git a/src/gn/vector_utils_unittest.cc b/src/gn/vector_utils_unittest.cc
new file mode 100644 (file)
index 0000000..c7cee01
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/vector_utils.h"
+
+#include "util/test/test.h"
+
+#include <string>
+
+TEST(VectorSetSorter, AsVectorWithStrings) {
+  VectorSetSorter<std::string> sorter;
+
+  std::vector<std::string> input = {
+      "World!", "Hello", "bonjour", "Hello", "monde!", "World!",
+  };
+
+  sorter.Add(input.begin(), input.end());
+  auto result = sorter.AsVector();
+
+  ASSERT_EQ(result.size(), 4u) << result.size();
+  EXPECT_STREQ(result[0].c_str(), "Hello");
+  EXPECT_STREQ(result[1].c_str(), "World!");
+  EXPECT_STREQ(result[2].c_str(), "bonjour");
+  EXPECT_STREQ(result[3].c_str(), "monde!");
+}
+
+TEST(VectorSetSorter, IterateOverWithStrings) {
+  VectorSetSorter<std::string> sorter;
+
+  std::vector<std::string> input = {
+      "World!", "Hello", "bonjour", "Hello", "monde!", "World!",
+  };
+
+  sorter.Add(input.begin(), input.end());
+
+  std::vector<std::string> result;
+
+  sorter.IterateOver(
+      [&result](const std::string& str) { result.push_back(str); });
+
+  ASSERT_EQ(result.size(), 4u) << result.size();
+  EXPECT_STREQ(result[0].c_str(), "Hello");
+  EXPECT_STREQ(result[1].c_str(), "World!");
+  EXPECT_STREQ(result[2].c_str(), "bonjour");
+  EXPECT_STREQ(result[3].c_str(), "monde!");
+}
diff --git a/src/gn/version.cc b/src/gn/version.cc
new file mode 100644 (file)
index 0000000..0ed25f2
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/version.h"
+#include <iostream>
+#include <string_view>
+#include <tuple>
+
+#include "base/strings/string_number_conversions.h"
+
+using namespace std::literals;
+
+constexpr std::string_view kDot = "."sv;
+
+Version::Version(int major, int minor, int patch)
+    : major_(major), minor_(minor), patch_(patch) {}
+
+// static
+std::optional<Version> Version::FromString(std::string s) {
+  int major = 0, minor = 0, patch = 0;
+  // First, parse the major version.
+  size_t major_begin = 0;
+  if (size_t major_end = s.find(kDot, major_begin);
+      major_end != std::string::npos) {
+    if (!base::StringToInt(s.substr(major_begin, major_end - major_begin),
+                           &major))
+      return {};
+    // Then, parse the minor version.
+    size_t minor_begin = major_end + kDot.size();
+    if (size_t minor_end = s.find(kDot, minor_begin);
+        minor_end != std::string::npos) {
+      if (!base::StringToInt(s.substr(minor_begin, minor_end - minor_begin),
+                             &minor))
+        return {};
+      // Finally, parse the patch version.
+      size_t patch_begin = minor_end + kDot.size();
+      if (!base::StringToInt(s.substr(patch_begin, std::string::npos), &patch))
+        return {};
+      return Version(major, minor, patch);
+    }
+  }
+  return {};
+}
+
+bool Version::operator==(const Version& other) const {
+  return other.major_ == major_ && other.minor_ == minor_ &&
+         other.patch_ == patch_;
+}
+
+bool Version::operator<(const Version& other) const {
+  return std::tie(major_, minor_, patch_) <
+         std::tie(other.major_, other.minor_, other.patch_);
+}
+
+bool Version::operator!=(const Version& other) const {
+  return !(*this == other);
+}
+
+bool Version::operator>=(const Version& other) const {
+  return !(*this < other);
+}
+
+bool Version::operator>(const Version& other) const {
+  return other < *this;
+}
+
+bool Version::operator<=(const Version& other) const {
+  return !(*this > other);
+}
+
+std::string Version::Describe() const {
+  std::string ret;
+  ret += base::IntToString(major_);
+  ret += kDot;
+  ret += base::IntToString(minor_);
+  ret += kDot;
+  ret += base::IntToString(patch_);
+  return ret;
+}
diff --git a/src/gn/version.h b/src/gn/version.h
new file mode 100644 (file)
index 0000000..7fcf81d
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VERSION_H_
+#define TOOLS_GN_VERSION_H_
+
+#include <optional>
+#include <string>
+
+// Represents a semantic version.
+class Version {
+ public:
+  Version(int major, int minor, int patch);
+
+  static std::optional<Version> FromString(std::string s);
+
+  int major() const { return major_; }
+  int minor() const { return minor_; }
+  int patch() const { return patch_; }
+
+  bool operator==(const Version& other) const;
+  bool operator<(const Version& other) const;
+  bool operator!=(const Version& other) const;
+  bool operator>=(const Version& other) const;
+  bool operator>(const Version& other) const;
+  bool operator<=(const Version& other) const;
+
+  std::string Describe() const;
+
+ private:
+  int major_;
+  int minor_;
+  int patch_;
+};
+
+#endif  // TOOLS_GN_VERSION_H_
diff --git a/src/gn/version_unittest.cc b/src/gn/version_unittest.cc
new file mode 100644 (file)
index 0000000..d1530f3
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/version.h"
+
+#include "util/test/test.h"
+
+TEST(VersionTest, FromString) {
+  Version v0_0_1{0, 0, 1};
+  ASSERT_EQ(Version::FromString("0.0.1"), v0_0_1);
+  Version v0_1_0{0, 1, 0};
+  ASSERT_EQ(Version::FromString("0.1.0"), v0_1_0);
+  Version v1_0_0{1, 0, 0};
+  ASSERT_EQ(Version::FromString("1.0.0"), v1_0_0);
+}
+
+TEST(VersionTest, Comparison) {
+  Version v0_0_1{0, 0, 1};
+  Version v0_1_0{0, 1, 0};
+  ASSERT_TRUE(v0_0_1 == v0_0_1);
+  ASSERT_TRUE(v0_0_1 != v0_1_0);
+  ASSERT_TRUE(v0_0_1 <= v0_0_1);
+  ASSERT_TRUE(v0_0_1 <= v0_1_0);
+  ASSERT_TRUE(v0_0_1 < v0_1_0);
+  ASSERT_TRUE(v0_0_1 >= v0_0_1);
+  ASSERT_TRUE(v0_1_0 > v0_0_1);
+  ASSERT_TRUE(v0_1_0 >= v0_0_1);
+}
+
+TEST(VersionTest, Describe) {
+  ASSERT_EQ(Version::FromString("0.0.1")->Describe(), "0.0.1");
+}
diff --git a/src/gn/visibility.cc b/src/gn/visibility.cc
new file mode 100644 (file)
index 0000000..eb99a27
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visibility.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "gn/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/item.h"
+#include "gn/label.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+
+Visibility::Visibility() = default;
+
+Visibility::~Visibility() = default;
+
+bool Visibility::Set(const SourceDir& current_dir,
+                     const std::string_view& source_root,
+                     const Value& value,
+                     Err* err) {
+  patterns_.clear();
+
+  if (!value.VerifyTypeIs(Value::LIST, err)) {
+    CHECK(err->has_error());
+    return false;
+  }
+
+  for (const auto& item : value.list_value()) {
+    patterns_.push_back(
+        LabelPattern::GetPattern(current_dir, source_root, item, err));
+    if (err->has_error())
+      return false;
+  }
+  return true;
+}
+
+void Visibility::SetPublic() {
+  patterns_.clear();
+  patterns_.push_back(LabelPattern(LabelPattern::RECURSIVE_DIRECTORY,
+                                   SourceDir(), std::string(), Label()));
+}
+
+void Visibility::SetPrivate(const SourceDir& current_dir) {
+  patterns_.clear();
+  patterns_.push_back(LabelPattern(LabelPattern::DIRECTORY, current_dir,
+                                   std::string(), Label()));
+}
+
+bool Visibility::CanSeeMe(const Label& label) const {
+  return LabelPattern::VectorMatches(patterns_, label);
+}
+
+std::string Visibility::Describe(int indent, bool include_brackets) const {
+  std::string outer_indent_string(indent, ' ');
+
+  if (patterns_.empty())
+    return outer_indent_string + "[] (no visibility)\n";
+
+  std::string result;
+
+  std::string inner_indent_string = outer_indent_string;
+  if (include_brackets) {
+    result += outer_indent_string + "[\n";
+    // Indent the insides more if brackets are requested.
+    inner_indent_string += "  ";
+  }
+
+  for (const auto& pattern : patterns_)
+    result += inner_indent_string + pattern.Describe() + "\n";
+
+  if (include_brackets)
+    result += outer_indent_string + "]\n";
+  return result;
+}
+
+std::unique_ptr<base::Value> Visibility::AsValue() const {
+  auto res = std::make_unique<base::ListValue>();
+  for (const auto& pattern : patterns_)
+    res->AppendString(pattern.Describe());
+  return std::move(res);
+}
+
+// static
+bool Visibility::CheckItemVisibility(const Item* from,
+                                     const Item* to,
+                                     Err* err) {
+  if (!to->visibility().CanSeeMe(from->label())) {
+    std::string to_label = to->label().GetUserVisibleName(false);
+    *err = Err(from->defined_from(), "Dependency not allowed.",
+               "The item " + from->label().GetUserVisibleName(false) +
+                   "\n"
+                   "can not depend on " +
+                   to_label +
+                   "\n"
+                   "because it is not in " +
+                   to_label +
+                   "'s visibility list: " + to->visibility().Describe(0, true));
+    return false;
+  }
+  return true;
+}
+
+// static
+bool Visibility::FillItemVisibility(Item* item, Scope* scope, Err* err) {
+  const Value* vis_value = scope->GetValue(variables::kVisibility, true);
+  if (vis_value)
+    item->visibility().Set(
+        scope->GetSourceDir(),
+        scope->settings()->build_settings()->root_path_utf8(), *vis_value, err);
+  else  // Default to public.
+    item->visibility().SetPublic();
+  return !err->has_error();
+}
diff --git a/src/gn/visibility.h b/src/gn/visibility.h
new file mode 100644 (file)
index 0000000..fc16731
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VISIBILITY_H_
+#define TOOLS_GN_VISIBILITY_H_
+
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "base/macros.h"
+#include "gn/label_pattern.h"
+#include "gn/source_dir.h"
+
+namespace base {
+class Value;
+}
+
+class Err;
+class Item;
+class Label;
+class Scope;
+class Value;
+
+class Visibility {
+ public:
+  // Defaults to private visibility (only the current file).
+  Visibility();
+  ~Visibility();
+
+  // Set the visibility to the thing specified by the given value. On failure,
+  // returns false and sets the error.
+  bool Set(const SourceDir& current_dir,
+           const std::string_view& source_root,
+           const Value& value,
+           Err* err);
+
+  // Sets the visibility to be public.
+  void SetPublic();
+
+  // Sets the visibility to be private to the given directory.
+  void SetPrivate(const SourceDir& current_dir);
+
+  // Returns true if the target with the given label can depend on one with the
+  // current visibility.
+  bool CanSeeMe(const Label& label) const;
+
+  // Returns a string listing the visibility. |indent| number of spaces will
+  // be added on the left side of the output. If |include_brackets| is set, the
+  // result will be wrapped in "[ ]" and the contents further indented. The
+  // result will end in a newline.
+  std::string Describe(int indent, bool include_brackets) const;
+
+  // Returns value representation of this visibility
+  std::unique_ptr<base::Value> AsValue() const;
+
+  // Helper function to check visibility between the given two items. If
+  // to is invisible to from, returns false and sets the error.
+  static bool CheckItemVisibility(const Item* from, const Item* to, Err* err);
+
+  // Helper function to fill an item's visibility from the "visibility" value
+  // in the current scope.
+  static bool FillItemVisibility(Item* item, Scope* scope, Err* err);
+
+ private:
+  std::vector<LabelPattern> patterns_;
+
+  DISALLOW_COPY_AND_ASSIGN(Visibility);
+};
+
+#endif  // TOOLS_GN_VISIBILITY_H_
diff --git a/src/gn/visibility_unittest.cc b/src/gn/visibility_unittest.cc
new file mode 100644 (file)
index 0000000..0122119
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visibility.h"
+#include "gn/err.h"
+#include "gn/label.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+#include "util/test/test.h"
+
+TEST(Visibility, CanSeeMe) {
+  Value list(nullptr, Value::LIST);
+  list.list_value().push_back(Value(nullptr, "//rec/*"));    // Recursive.
+  list.list_value().push_back(Value(nullptr, "//dir:*"));    // One dir.
+  list.list_value().push_back(Value(nullptr, "//my:name"));  // Exact match.
+
+  Err err;
+  Visibility vis;
+  ASSERT_TRUE(vis.Set(SourceDir("//"), std::string_view(), list, &err));
+
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//random/"), "thing")));
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//my/"), "notname")));
+
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//my/"), "name")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//rec/"), "anything")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//rec/a/"), "anything")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//rec/b/"), "anything")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//dir/"), "anything")));
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//dir/a/"), "anything")));
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//directory/"), "anything")));
+}
+
+TEST(Visibility, Public) {
+  Err err;
+  Visibility vis;
+
+  Value list(nullptr, Value::LIST);
+  list.list_value().push_back(Value(nullptr, "*"));
+  ASSERT_TRUE(vis.Set(SourceDir("//"), std::string_view(), list, &err));
+
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//random/"), "thing")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("//"), "")));
+}
+
+TEST(Visibility, Private) {
+  Err err;
+  Visibility vis;
+  ASSERT_TRUE(vis.Set(SourceDir("//"), std::string_view(),
+                      Value(nullptr, Value::LIST), &err));
+
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//random/"), "thing")));
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//"), "")));
+}
+
+TEST(Visibility, AboveSourceDir) {
+  std::string source_root = "/foo/bar/baz/";
+  SourceDir cur_dir("//");
+
+  Err err;
+  Visibility vis;
+
+  Value list(nullptr, Value::LIST);
+  list.list_value().push_back(Value(nullptr, "../../*"));
+  ASSERT_TRUE(vis.Set(cur_dir, source_root, list, &err));
+
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("//random/"), "thing")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("/foo/"), "foo")));
+  EXPECT_TRUE(vis.CanSeeMe(Label(SourceDir("/foo/bar/"), "bar")));
+  EXPECT_FALSE(vis.CanSeeMe(Label(SourceDir("/nowhere/"), "foo")));
+}
diff --git a/src/gn/visual_studio_utils.cc b/src/gn/visual_studio_utils.cc
new file mode 100644 (file)
index 0000000..5282d0e
--- /dev/null
@@ -0,0 +1,127 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visual_studio_utils.h"
+
+#include <vector>
+
+#include "base/md5.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+
+CompilerOptions::CompilerOptions() = default;
+
+CompilerOptions::~CompilerOptions() = default;
+
+LinkerOptions::LinkerOptions() = default;
+
+LinkerOptions::~LinkerOptions() = default;
+
+std::string MakeGuid(const std::string& entry_path, const std::string& seed) {
+  std::string str = base::ToUpperASCII(base::MD5String(seed + entry_path));
+  return '{' + str.substr(0, 8) + '-' + str.substr(8, 4) + '-' +
+         str.substr(12, 4) + '-' + str.substr(16, 4) + '-' +
+         str.substr(20, 12) + '}';
+}
+
+#define SetOption(condition, member, value) \
+  if (condition) {                          \
+    options->member = value;                \
+    return;                                 \
+  }
+
+#define AppendOption(condition, member, value, separator) \
+  if (condition) {                                        \
+    options->member += value + separator;                 \
+    return;                                               \
+  }
+
+void ParseCompilerOption(const std::string& cflag, CompilerOptions* options) {
+  if (cflag.size() > 2 && cflag[0] == '/') {
+    switch (cflag[1]) {
+      case 'F':
+        AppendOption(cflag.size() > 3 && cflag[2] == 'I', forced_include_files,
+                     cflag.substr(3), ';') break;
+
+      case 'G':
+        if (cflag[2] == 'S') {
+          SetOption(cflag.size() == 3, buffer_security_check, "true")
+              SetOption(cflag.size() == 4 && cflag[3] == '-',
+                        buffer_security_check, "false")
+        }
+        break;
+
+      case 'M':
+        switch (cflag[2]) {
+          case 'D':
+            SetOption(cflag.size() == 3, runtime_library, "MultiThreadedDLL")
+                SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
+                          "MultiThreadedDebugDLL") break;
+
+          case 'T':
+            SetOption(cflag.size() == 3, runtime_library, "MultiThreaded")
+                SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
+                          "MultiThreadedDebug") break;
+        }
+        break;
+
+      case 'O':
+        switch (cflag[2]) {
+          case '1':
+            SetOption(cflag.size() == 3, optimization, "MinSpace") break;
+
+          case '2':
+            SetOption(cflag.size() == 3, optimization, "MaxSpeed") break;
+
+          case 'd':
+            SetOption(cflag.size() == 3, optimization, "Disabled") break;
+
+          case 'x':
+            SetOption(cflag.size() == 3, optimization, "Full") break;
+        }
+        break;
+
+      case 'T':
+        // Skip flags that cause treating all source files as C and C++ files.
+        if (cflag.size() == 3 && (cflag[2] == 'C' || cflag[2] == 'P'))
+          return;
+        break;
+
+      case 'W':
+        switch (cflag[2]) {
+          case '0':
+          case '1':
+          case '2':
+          case '3':
+          case '4':
+            SetOption(cflag.size() == 3, warning_level,
+                      std::string("Level") + cflag[2]) break;
+
+          case 'X':
+            SetOption(cflag.size() == 3, treat_warning_as_error, "true") break;
+        }
+        break;
+
+      case 'w':
+        AppendOption(cflag.size() > 3 && cflag[2] == 'd',
+                     disable_specific_warnings, cflag.substr(3), ';') break;
+    }
+  }
+
+  // Put everything else into additional_options.
+  options->additional_options += cflag + ' ';
+}
+
+// Parses |ldflags| value and stores it in |options|.
+void ParseLinkerOption(const std::string& ldflag, LinkerOptions* options) {
+  const char kSubsytemPrefix[] = "/SUBSYSTEM:";
+  if (base::StartsWith(ldflag, kSubsytemPrefix, base::CompareCase::SENSITIVE)) {
+    const std::string subsystem(
+        ldflag.begin() + std::string(kSubsytemPrefix).length(), ldflag.end());
+    const std::vector<std::string> tokens = base::SplitString(
+        subsystem, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    if (!tokens.empty())
+      options->subsystem = tokens[0];
+  }
+}
diff --git a/src/gn/visual_studio_utils.h b/src/gn/visual_studio_utils.h
new file mode 100644 (file)
index 0000000..319428f
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VISUAL_STUDIO_UTILS_H_
+#define TOOLS_GN_VISUAL_STUDIO_UTILS_H_
+
+#include <string>
+
+// Some compiler options which will be written to project file. We don't need to
+// specify all options because generated project file is going to be used only
+// for compilation of single file. For real build ninja files are used.
+struct CompilerOptions {
+  CompilerOptions();
+  ~CompilerOptions();
+
+  std::string additional_options;
+  std::string buffer_security_check;
+  std::string forced_include_files;
+  std::string disable_specific_warnings;
+  std::string optimization;
+  std::string runtime_library;
+  std::string treat_warning_as_error;
+  std::string warning_level;
+};
+
+// Some linker options which will be written to project file. We don't need to
+// specify all options because generated project file is going to be used only
+// for compilation of single file. For real build ninja files are used.
+struct LinkerOptions {
+  LinkerOptions();
+  ~LinkerOptions();
+
+  std::string subsystem;
+};
+
+// Generates something which looks like a GUID, but depends only on the name and
+// seed. This means the same name / seed will always generate the same GUID, so
+// that projects and solutions which refer to each other can explicitly
+// determine the GUID to refer to explicitly. It also means that the GUID will
+// not change when the project for a target is rebuilt.
+std::string MakeGuid(const std::string& entry_path, const std::string& seed);
+
+// Parses |cflag| value and stores it in |options|.
+void ParseCompilerOption(const std::string& cflag, CompilerOptions* options);
+
+// Parses |ldflags| value and stores it in |options|.
+void ParseLinkerOption(const std::string& ldflag, LinkerOptions* options);
+
+#endif  // TOOLS_GN_VISUAL_STUDIO_UTILS_H_
diff --git a/src/gn/visual_studio_utils_unittest.cc b/src/gn/visual_studio_utils_unittest.cc
new file mode 100644 (file)
index 0000000..bf84a9e
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visual_studio_utils.h"
+
+#include "base/strings/string_util.h"
+#include "util/test/test.h"
+
+TEST(VisualStudioUtils, MakeGuid) {
+  std::string pattern = "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}";
+  std::string guid = MakeGuid(__FILE__, "foo");
+  ASSERT_EQ(pattern.size(), guid.size());
+  for (size_t i = 0; i < pattern.size(); ++i) {
+    if (pattern[i] == 'x')
+      ASSERT_TRUE(base::IsAsciiAlpha(guid[i]) || base::IsAsciiDigit(guid[i]));
+    else
+      ASSERT_EQ(pattern[i], guid[i]);
+  }
+
+  // Calling function again should produce the same GUID.
+  ASSERT_EQ(guid, MakeGuid(__FILE__, "foo"));
+
+  // GUIDs should be different if path or seed is different.
+  ASSERT_NE(guid, MakeGuid(std::string(__FILE__) + ".txt", "foo"));
+  ASSERT_NE(guid, MakeGuid(__FILE__, "bar"));
+}
+
+TEST(VisualStudioUtils, ParseCompilerOption) {
+  CompilerOptions options;
+  ParseCompilerOption("/FIinclude.h", &options);
+  ParseCompilerOption("/FIC:/path/file.h", &options);
+  ASSERT_EQ("include.h;C:/path/file.h;", options.forced_include_files);
+
+  CHECK(options.buffer_security_check.empty());
+  ParseCompilerOption("/GS", &options);
+  ASSERT_EQ("true", options.buffer_security_check);
+  ParseCompilerOption("/GS-", &options);
+  ASSERT_EQ("false", options.buffer_security_check);
+
+  CHECK(options.runtime_library.empty());
+  ParseCompilerOption("/MD", &options);
+  ASSERT_EQ("MultiThreadedDLL", options.runtime_library);
+  ParseCompilerOption("/MDd", &options);
+  ASSERT_EQ("MultiThreadedDebugDLL", options.runtime_library);
+  ParseCompilerOption("/MT", &options);
+  ASSERT_EQ("MultiThreaded", options.runtime_library);
+  ParseCompilerOption("/MTd", &options);
+  ASSERT_EQ("MultiThreadedDebug", options.runtime_library);
+
+  CHECK(options.optimization.empty());
+  ParseCompilerOption("/O1", &options);
+  ASSERT_EQ("MinSpace", options.optimization);
+  ParseCompilerOption("/O2", &options);
+  ASSERT_EQ("MaxSpeed", options.optimization);
+  ParseCompilerOption("/Od", &options);
+  ASSERT_EQ("Disabled", options.optimization);
+  ParseCompilerOption("/Ox", &options);
+  ASSERT_EQ("Full", options.optimization);
+
+  CHECK(options.additional_options.empty());
+  ParseCompilerOption("/TC", &options);
+  ASSERT_TRUE(options.additional_options.empty());
+  ParseCompilerOption("/TP", &options);
+  ASSERT_TRUE(options.additional_options.empty());
+
+  CHECK(options.warning_level.empty());
+  ParseCompilerOption("/W0", &options);
+  ASSERT_EQ("Level0", options.warning_level);
+  ParseCompilerOption("/W1", &options);
+  ASSERT_EQ("Level1", options.warning_level);
+  ParseCompilerOption("/W2", &options);
+  ASSERT_EQ("Level2", options.warning_level);
+  ParseCompilerOption("/W3", &options);
+  ASSERT_EQ("Level3", options.warning_level);
+  ParseCompilerOption("/W4", &options);
+  ASSERT_EQ("Level4", options.warning_level);
+
+  CHECK(options.treat_warning_as_error.empty());
+  ParseCompilerOption("/WX", &options);
+  ASSERT_EQ("true", options.treat_warning_as_error);
+
+  CHECK(options.disable_specific_warnings.empty());
+  ParseCompilerOption("/wd1234", &options);
+  ParseCompilerOption("/wd56", &options);
+  ASSERT_EQ("1234;56;", options.disable_specific_warnings);
+
+  CHECK(options.additional_options.empty());
+  ParseCompilerOption("/MP", &options);
+  ParseCompilerOption("/bigobj", &options);
+  ParseCompilerOption("/Zc:sizedDealloc", &options);
+  ASSERT_EQ("/MP /bigobj /Zc:sizedDealloc ", options.additional_options);
+}
+
+TEST(VisualStudioUtils, ParseLinkerOption) {
+  LinkerOptions options;
+  ParseLinkerOption("/SUBSYSTEM:CONSOLE,5.02h", &options);
+  ASSERT_EQ("CONSOLE", options.subsystem);
+
+  ParseLinkerOption("/SUBSYSTEM:WINDOWS", &options);
+  ASSERT_EQ("WINDOWS", options.subsystem);
+}
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc
new file mode 100644 (file)
index 0000000..6ee56e8
--- /dev/null
@@ -0,0 +1,916 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visual_studio_writer.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/containers/queue.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gn/builder.h"
+#include "gn/commands.h"
+#include "gn/config.h"
+#include "gn/config_values_extractors.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/label_pattern.h"
+#include "gn/parse_tree.h"
+#include "gn/path_output.h"
+#include "gn/standard_out.h"
+#include "gn/target.h"
+#include "gn/variables.h"
+#include "gn/visual_studio_utils.h"
+#include "gn/xml_element_writer.h"
+
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#endif
+
+namespace {
+
+struct SemicolonSeparatedWriter {
+  void operator()(const std::string& value, std::ostream& out) const {
+    out << XmlEscape(value) + ';';
+  }
+};
+
+struct IncludeDirWriter {
+  explicit IncludeDirWriter(PathOutput& path_output)
+      : path_output_(path_output) {}
+  ~IncludeDirWriter() = default;
+
+  void operator()(const SourceDir& dir, std::ostream& out) const {
+    path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH);
+    out << ";";
+  }
+
+  PathOutput& path_output_;
+};
+
+struct SourceFileWriter {
+  SourceFileWriter(PathOutput& path_output, const SourceFile& source_file)
+      : path_output_(path_output), source_file_(source_file) {}
+  ~SourceFileWriter() = default;
+
+  void operator()(std::ostream& out) const {
+    path_output_.WriteFile(out, source_file_);
+  }
+
+  PathOutput& path_output_;
+  const SourceFile& source_file_;
+};
+
+const char kToolsetVersionVs2013[] = "v120";               // Visual Studio 2013
+const char kToolsetVersionVs2015[] = "v140";               // Visual Studio 2015
+const char kToolsetVersionVs2017[] = "v141";               // Visual Studio 2017
+const char kToolsetVersionVs2019[] = "v142";               // Visual Studio 2019
+const char kProjectVersionVs2013[] = "12.0";               // Visual Studio 2013
+const char kProjectVersionVs2015[] = "14.0";               // Visual Studio 2015
+const char kProjectVersionVs2017[] = "15.0";               // Visual Studio 2017
+const char kProjectVersionVs2019[] = "16.0";               // Visual Studio 2019
+const char kVersionStringVs2013[] = "Visual Studio 2013";  // Visual Studio 2013
+const char kVersionStringVs2015[] = "Visual Studio 2015";  // Visual Studio 2015
+const char kVersionStringVs2017[] = "Visual Studio 2017";  // Visual Studio 2017
+const char kVersionStringVs2019[] = "Visual Studio 2019";  // Visual Studio 2019
+const char kWindowsKitsVersion[] = "10";                   // Windows 10 SDK
+const char kWindowsKitsDefaultVersion[] = "10";            // Windows 10 SDK
+
+const char kGuidTypeProject[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
+const char kGuidTypeFolder[] = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
+const char kGuidSeedProject[] = "project";
+const char kGuidSeedFolder[] = "folder";
+const char kGuidSeedFilter[] = "filter";
+
+const char kConfigurationName[] = "GN";
+
+const char kCharSetUnicode[] = "_UNICODE";
+const char kCharSetMultiByte[] = "_MBCS";
+
+std::string GetWindowsKitsIncludeDirs(const std::string& win_kit) {
+  std::string kits_path;
+
+#if defined(OS_WIN)
+  const char16_t* const subkeys[] = {
+      u"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots",
+      u"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots"};
+
+  std::u16string value_name =
+      base::ASCIIToUTF16("KitsRoot") + base::ASCIIToUTF16(kWindowsKitsVersion);
+
+  for (const char16_t* subkey : subkeys) {
+    base::win::RegKey key(HKEY_LOCAL_MACHINE, subkey, KEY_READ);
+    std::u16string value;
+    if (key.ReadValue(value_name.c_str(), &value) == ERROR_SUCCESS) {
+      kits_path = base::UTF16ToUTF8(value);
+      break;
+    }
+  }
+#endif  // OS_WIN
+
+  if (kits_path.empty()) {
+    kits_path = std::string("C:\\Program Files (x86)\\Windows Kits\\") +
+                kWindowsKitsVersion + "\\";
+  }
+
+  const std::string kit_prefix = kits_path + "Include\\" + win_kit + "\\";
+  return kit_prefix + "shared;" + kit_prefix + "um;" + kit_prefix + "winrt;";
+}
+
+std::string GetConfigurationType(const Target* target, Err* err) {
+  switch (target->output_type()) {
+    case Target::EXECUTABLE:
+      return "Application";
+    case Target::SHARED_LIBRARY:
+    case Target::LOADABLE_MODULE:
+      return "DynamicLibrary";
+    case Target::STATIC_LIBRARY:
+    case Target::SOURCE_SET:
+      return "StaticLibrary";
+    case Target::GROUP:
+      return "Utility";
+
+    default:
+      *err = Err(Location(),
+                 "Visual Studio doesn't support '" + target->label().name() +
+                     "' target output type: " +
+                     Target::GetStringForOutputType(target->output_type()));
+      return std::string();
+  }
+}
+
+void ParseCompilerOptions(const std::vector<std::string>& cflags,
+                          CompilerOptions* options) {
+  for (const std::string& flag : cflags)
+    ParseCompilerOption(flag, options);
+}
+
+void ParseCompilerOptions(const Target* target, CompilerOptions* options) {
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    ParseCompilerOptions(iter.cur().cflags(), options);
+    ParseCompilerOptions(iter.cur().cflags_c(), options);
+    ParseCompilerOptions(iter.cur().cflags_cc(), options);
+  }
+}
+
+void ParseLinkerOptions(const std::vector<std::string>& ldflags,
+                        LinkerOptions* options) {
+  for (const std::string& flag : ldflags)
+    ParseLinkerOption(flag, options);
+}
+
+void ParseLinkerOptions(const Target* target, LinkerOptions* options) {
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    ParseLinkerOptions(iter.cur().ldflags(), options);
+  }
+}
+
+// Returns a string piece pointing into the input string identifying the parent
+// directory path, excluding the last slash. Note that the input pointer must
+// outlive the output.
+std::string_view FindParentDir(const std::string* path) {
+  DCHECK(path && !path->empty());
+  for (int i = static_cast<int>(path->size()) - 2; i >= 0; --i) {
+    if (IsSlash((*path)[i]))
+      return std::string_view(path->data(), i);
+  }
+  return std::string_view();
+}
+
+bool FilterTargets(const BuildSettings* build_settings,
+                   const Builder& builder,
+                   const std::string& filters,
+                   bool no_deps,
+                   std::vector<const Target*>* targets,
+                   Err* err) {
+  if (filters.empty()) {
+    *targets = builder.GetAllResolvedTargets();
+    return true;
+  }
+
+  std::vector<LabelPattern> patterns;
+  if (!commands::FilterPatternsFromString(build_settings, filters, &patterns,
+                                          err))
+    return false;
+
+  commands::FilterTargetsByPatterns(builder.GetAllResolvedTargets(), patterns,
+                                    targets);
+
+  if (no_deps)
+    return true;
+
+  std::set<Label> labels;
+  base::queue<const Target*> to_process;
+  for (const Target* target : *targets) {
+    labels.insert(target->label());
+    to_process.push(target);
+  }
+
+  while (!to_process.empty()) {
+    const Target* target = to_process.front();
+    to_process.pop();
+    for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
+      if (labels.find(pair.label) == labels.end()) {
+        targets->push_back(pair.ptr);
+        to_process.push(pair.ptr);
+        labels.insert(pair.label);
+      }
+    }
+  }
+
+  return true;
+}
+
+bool UnicodeTarget(const Target* target) {
+  for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+    for (const std::string& define : it.cur().defines()) {
+      if (define == kCharSetUnicode)
+        return true;
+      if (define == kCharSetMultiByte)
+        return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name,
+                                                 const std::string& _path,
+                                                 const std::string& _guid)
+    : name(_name), path(_path), guid(_guid), parent_folder(nullptr) {}
+
+VisualStudioWriter::SolutionEntry::~SolutionEntry() = default;
+
+VisualStudioWriter::SolutionProject::SolutionProject(
+    const std::string& _name,
+    const std::string& _path,
+    const std::string& _guid,
+    const std::string& _label_dir_path,
+    const std::string& _config_platform)
+    : SolutionEntry(_name, _path, _guid),
+      label_dir_path(_label_dir_path),
+      config_platform(_config_platform) {
+  // Make sure all paths use the same drive letter case. This is especially
+  // important when searching for the common path prefix.
+  label_dir_path[0] = base::ToUpperASCII(label_dir_path[0]);
+}
+
+VisualStudioWriter::SolutionProject::~SolutionProject() = default;
+
+VisualStudioWriter::SourceFileCompileTypePair::SourceFileCompileTypePair(
+    const SourceFile* _file,
+    const char* _compile_type)
+    : file(_file), compile_type(_compile_type) {}
+
+VisualStudioWriter::SourceFileCompileTypePair::~SourceFileCompileTypePair() =
+    default;
+
+VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings,
+                                       const char* config_platform,
+                                       Version version,
+                                       const std::string& win_kit)
+    : build_settings_(build_settings),
+      config_platform_(config_platform),
+      ninja_path_output_(build_settings->build_dir(),
+                         build_settings->root_path_utf8(),
+                         EscapingMode::ESCAPE_NINJA_COMMAND),
+      windows_sdk_version_(win_kit) {
+  DCHECK(!win_kit.empty());
+
+  switch (version) {
+    case Version::Vs2013:
+      project_version_ = kProjectVersionVs2013;
+      toolset_version_ = kToolsetVersionVs2013;
+      version_string_ = kVersionStringVs2013;
+      break;
+    case Version::Vs2015:
+      project_version_ = kProjectVersionVs2015;
+      toolset_version_ = kToolsetVersionVs2015;
+      version_string_ = kVersionStringVs2015;
+      break;
+    case Version::Vs2017:
+      project_version_ = kProjectVersionVs2017;
+      toolset_version_ = kToolsetVersionVs2017;
+      version_string_ = kVersionStringVs2017;
+      break;
+    case Version::Vs2019:
+      project_version_ = kProjectVersionVs2019;
+      toolset_version_ = kToolsetVersionVs2019;
+      version_string_ = kVersionStringVs2019;
+      break;
+    default:
+      NOTREACHED() << "Not a valid Visual Studio Version: " << version;
+  }
+
+  windows_kits_include_dirs_ = GetWindowsKitsIncludeDirs(win_kit);
+}
+
+VisualStudioWriter::~VisualStudioWriter() = default;
+
+// static
+bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+                                          const Builder& builder,
+                                          Version version,
+                                          const std::string& sln_name,
+                                          const std::string& filters,
+                                          const std::string& win_sdk,
+                                          const std::string& ninja_extra_args,
+                                          bool no_deps,
+                                          Err* err) {
+  std::vector<const Target*> targets;
+  if (!FilterTargets(build_settings, builder, filters, no_deps, &targets, err))
+    return false;
+
+  std::string win_kit = kWindowsKitsDefaultVersion;
+  if (!win_sdk.empty())
+    win_kit = win_sdk;
+
+  const char* config_platform = "Win32";
+
+  // Assume the "target_cpu" variable does not change between different
+  // toolchains.
+  if (!targets.empty()) {
+    const Scope* scope = targets.front()->settings()->base_config();
+    const Value* target_cpu_value = scope->GetValue(variables::kTargetCpu);
+    if (target_cpu_value != nullptr &&
+        target_cpu_value->string_value() == "x64")
+      config_platform = "x64";
+  }
+
+  VisualStudioWriter writer(build_settings, config_platform, version, win_kit);
+  writer.projects_.reserve(targets.size());
+  writer.folders_.reserve(targets.size());
+
+  for (const Target* target : targets) {
+    // Skip actions and bundle targets.
+    if (target->output_type() == Target::ACTION ||
+        target->output_type() == Target::ACTION_FOREACH ||
+        target->output_type() == Target::BUNDLE_DATA ||
+        target->output_type() == Target::COPY_FILES ||
+        target->output_type() == Target::CREATE_BUNDLE ||
+        target->output_type() == Target::GENERATED_FILE) {
+      continue;
+    }
+
+    if (!writer.WriteProjectFiles(target, ninja_extra_args, err))
+      return false;
+  }
+
+  if (writer.projects_.empty()) {
+    *err = Err(Location(), "No Visual Studio projects generated.");
+    return false;
+  }
+
+  // Sort projects so they appear always in the same order in solution file.
+  // Otherwise solution file is rewritten and reloaded by Visual Studio.
+  std::sort(writer.projects_.begin(), writer.projects_.end(),
+            [](const std::unique_ptr<SolutionProject>& a,
+               const std::unique_ptr<SolutionProject>& b) {
+              return a->path < b->path;
+            });
+
+  writer.ResolveSolutionFolders();
+  return writer.WriteSolutionFile(sln_name, err);
+}
+
+bool VisualStudioWriter::WriteProjectFiles(const Target* target,
+                                           const std::string& ninja_extra_args,
+                                           Err* err) {
+  std::string project_name = target->label().name();
+  const char* project_config_platform = config_platform_;
+  if (!target->settings()->is_default()) {
+    project_name += "_" + target->toolchain()->label().name();
+    const Value* value =
+        target->settings()->base_config()->GetValue(variables::kCurrentCpu);
+    if (value != nullptr && value->string_value() == "x64")
+      project_config_platform = "x64";
+    else
+      project_config_platform = "Win32";
+  }
+
+  SourceFile target_file =
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ)
+          .ResolveRelativeFile(Value(nullptr, project_name + ".vcxproj"), err);
+  if (target_file.is_null())
+    return false;
+
+  base::FilePath vcxproj_path = build_settings_->GetFullPath(target_file);
+  std::string vcxproj_path_str = FilePathToUTF8(vcxproj_path);
+
+  projects_.push_back(std::make_unique<SolutionProject>(
+      project_name, vcxproj_path_str,
+      MakeGuid(vcxproj_path_str, kGuidSeedProject),
+      FilePathToUTF8(build_settings_->GetFullPath(target->label().dir())),
+      project_config_platform));
+
+  std::stringstream vcxproj_string_out;
+  SourceFileCompileTypePairs source_types;
+  if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target,
+                                ninja_extra_args, &source_types, err)) {
+    projects_.pop_back();
+    return false;
+  }
+
+  // Only write the content to the file if it's different. That is
+  // both a performance optimization and more importantly, prevents
+  // Visual Studio from reloading the projects.
+  if (!WriteFileIfChanged(vcxproj_path, vcxproj_string_out.str(), err))
+    return false;
+
+  base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters");
+  std::stringstream filters_string_out;
+  WriteFiltersFileContents(filters_string_out, target, source_types);
+  return WriteFileIfChanged(filters_path, filters_string_out.str(), err);
+}
+
+bool VisualStudioWriter::WriteProjectFileContents(
+    std::ostream& out,
+    const SolutionProject& solution_project,
+    const Target* target,
+    const std::string& ninja_extra_args,
+    SourceFileCompileTypePairs* source_types,
+    Err* err) {
+  PathOutput path_output(
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
+      build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
+
+  out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
+  XmlElementWriter project(
+      out, "Project",
+      XmlAttributes("DefaultTargets", "Build")
+          .add("ToolsVersion", project_version_)
+          .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"));
+
+  {
+    std::unique_ptr<XmlElementWriter> configurations = project.SubElement(
+        "ItemGroup", XmlAttributes("Label", "ProjectConfigurations"));
+    std::unique_ptr<XmlElementWriter> project_config =
+        configurations->SubElement(
+            "ProjectConfiguration",
+            XmlAttributes("Include", std::string(kConfigurationName) + '|' +
+                                         solution_project.config_platform));
+    project_config->SubElement("Configuration")->Text(kConfigurationName);
+    project_config->SubElement("Platform")
+        ->Text(solution_project.config_platform);
+  }
+
+  {
+    std::unique_ptr<XmlElementWriter> globals =
+        project.SubElement("PropertyGroup", XmlAttributes("Label", "Globals"));
+    globals->SubElement("ProjectGuid")->Text(solution_project.guid);
+    globals->SubElement("Keyword")->Text("Win32Proj");
+    globals->SubElement("RootNamespace")->Text(target->label().name());
+    globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true");
+    globals->SubElement("PreferredToolArchitecture")->Text("x64");
+    globals->SubElement("WindowsTargetPlatformVersion")
+        ->Text(windows_sdk_version_);
+  }
+
+  project.SubElement(
+      "Import", XmlAttributes("Project",
+                              "$(VCTargetsPath)\\Microsoft.Cpp.Default.props"));
+
+  {
+    std::unique_ptr<XmlElementWriter> configuration = project.SubElement(
+        "PropertyGroup", XmlAttributes("Label", "Configuration"));
+    bool unicode_target = UnicodeTarget(target);
+    configuration->SubElement("CharacterSet")
+        ->Text(unicode_target ? "Unicode" : "MultiByte");
+    std::string configuration_type = GetConfigurationType(target, err);
+    if (configuration_type.empty())
+      return false;
+    configuration->SubElement("ConfigurationType")->Text(configuration_type);
+  }
+
+  {
+    std::unique_ptr<XmlElementWriter> locals =
+        project.SubElement("PropertyGroup", XmlAttributes("Label", "Locals"));
+    locals->SubElement("PlatformToolset")->Text(toolset_version_);
+  }
+
+  project.SubElement(
+      "Import",
+      XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.props"));
+  project.SubElement(
+      "Import",
+      XmlAttributes("Project",
+                    "$(VCTargetsPath)\\BuildCustomizations\\masm.props"));
+  project.SubElement("ImportGroup",
+                     XmlAttributes("Label", "ExtensionSettings"));
+
+  {
+    std::unique_ptr<XmlElementWriter> property_sheets = project.SubElement(
+        "ImportGroup", XmlAttributes("Label", "PropertySheets"));
+    property_sheets->SubElement(
+        "Import",
+        XmlAttributes(
+            "Condition",
+            "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')")
+            .add("Label", "LocalAppDataPlatform")
+            .add("Project",
+                 "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props"));
+  }
+
+  project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros"));
+
+  std::string ninja_target = GetNinjaTarget(target);
+
+  {
+    std::unique_ptr<XmlElementWriter> properties =
+        project.SubElement("PropertyGroup");
+    properties->SubElement("OutDir")->Text("$(SolutionDir)");
+    properties->SubElement("TargetName")->Text("$(ProjectName)");
+    if (target->output_type() != Target::GROUP) {
+      properties->SubElement("TargetPath")->Text("$(OutDir)\\" + ninja_target);
+    }
+  }
+
+  {
+    std::unique_ptr<XmlElementWriter> item_definitions =
+        project.SubElement("ItemDefinitionGroup");
+    {
+      std::unique_ptr<XmlElementWriter> cl_compile =
+          item_definitions->SubElement("ClCompile");
+      {
+        std::unique_ptr<XmlElementWriter> include_dirs =
+            cl_compile->SubElement("AdditionalIncludeDirectories");
+        RecursiveTargetConfigToStream<SourceDir>(
+            target, &ConfigValues::include_dirs, IncludeDirWriter(path_output),
+            include_dirs->StartContent(false));
+        include_dirs->Text(windows_kits_include_dirs_ +
+                           "$(VSInstallDir)\\VC\\atlmfc\\include;" +
+                           "%(AdditionalIncludeDirectories)");
+      }
+      CompilerOptions options;
+      ParseCompilerOptions(target, &options);
+      if (!options.additional_options.empty()) {
+        cl_compile->SubElement("AdditionalOptions")
+            ->Text(options.additional_options + "%(AdditionalOptions)");
+      }
+      if (!options.buffer_security_check.empty()) {
+        cl_compile->SubElement("BufferSecurityCheck")
+            ->Text(options.buffer_security_check);
+      }
+      cl_compile->SubElement("CompileAsWinRT")->Text("false");
+      cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase");
+      if (!options.disable_specific_warnings.empty()) {
+        cl_compile->SubElement("DisableSpecificWarnings")
+            ->Text(options.disable_specific_warnings +
+                   "%(DisableSpecificWarnings)");
+      }
+      cl_compile->SubElement("ExceptionHandling")->Text("false");
+      if (!options.forced_include_files.empty()) {
+        cl_compile->SubElement("ForcedIncludeFiles")
+            ->Text(options.forced_include_files);
+      }
+      cl_compile->SubElement("MinimalRebuild")->Text("false");
+      if (!options.optimization.empty())
+        cl_compile->SubElement("Optimization")->Text(options.optimization);
+      cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing");
+      {
+        std::unique_ptr<XmlElementWriter> preprocessor_definitions =
+            cl_compile->SubElement("PreprocessorDefinitions");
+        RecursiveTargetConfigToStream<std::string>(
+            target, &ConfigValues::defines, SemicolonSeparatedWriter(),
+            preprocessor_definitions->StartContent(false));
+        preprocessor_definitions->Text("%(PreprocessorDefinitions)");
+      }
+      if (!options.runtime_library.empty())
+        cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library);
+      if (!options.treat_warning_as_error.empty()) {
+        cl_compile->SubElement("TreatWarningAsError")
+            ->Text(options.treat_warning_as_error);
+      }
+      if (!options.warning_level.empty())
+        cl_compile->SubElement("WarningLevel")->Text(options.warning_level);
+    }
+
+    std::unique_ptr<XmlElementWriter> link =
+        item_definitions->SubElement("Link");
+    {
+      LinkerOptions options;
+      ParseLinkerOptions(target, &options);
+      if (!options.subsystem.empty())
+        link->SubElement("SubSystem")->Text(options.subsystem);
+    }
+
+    // We don't include resource compilation and other link options as ninja
+    // files are used to generate real build.
+  }
+
+  {
+    std::unique_ptr<XmlElementWriter> group = project.SubElement("ItemGroup");
+    std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop.
+
+    for (const SourceFile& file : target->sources()) {
+      const char* compile_type;
+      const char* tool_name = Tool::kToolNone;
+      if (target->GetOutputFilesForSource(file, &tool_name, &tool_outputs)) {
+        compile_type = "CustomBuild";
+        std::unique_ptr<XmlElementWriter> build = group->SubElement(
+            compile_type, "Include", SourceFileWriter(path_output, file));
+        build->SubElement("Command")->Text("call ninja.exe -C $(OutDir) " +
+                                           ninja_extra_args + " " +
+                                           tool_outputs[0].value());
+        build->SubElement("Outputs")->Text("$(OutDir)" +
+                                           tool_outputs[0].value());
+      } else {
+        compile_type = "None";
+        group->SubElement(compile_type, "Include",
+                          SourceFileWriter(path_output, file));
+      }
+      source_types->push_back(SourceFileCompileTypePair(&file, compile_type));
+    }
+  }
+
+  project.SubElement(
+      "Import",
+      XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets"));
+  project.SubElement(
+      "Import",
+      XmlAttributes("Project",
+                    "$(VCTargetsPath)\\BuildCustomizations\\masm.targets"));
+  project.SubElement("ImportGroup", XmlAttributes("Label", "ExtensionTargets"));
+
+  {
+    std::unique_ptr<XmlElementWriter> build =
+        project.SubElement("Target", XmlAttributes("Name", "Build"));
+    build->SubElement(
+        "Exec",
+        XmlAttributes("Command", "call ninja.exe -C $(OutDir) " +
+                                     ninja_extra_args + " " + ninja_target));
+  }
+
+  {
+    std::unique_ptr<XmlElementWriter> clean =
+        project.SubElement("Target", XmlAttributes("Name", "Clean"));
+    clean->SubElement(
+        "Exec",
+        XmlAttributes("Command",
+                      "call ninja.exe -C $(OutDir) -tclean " + ninja_target));
+  }
+
+  return true;
+}
+
+void VisualStudioWriter::WriteFiltersFileContents(
+    std::ostream& out,
+    const Target* target,
+    const SourceFileCompileTypePairs& source_types) {
+  out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
+  XmlElementWriter project(
+      out, "Project",
+      XmlAttributes("ToolsVersion", "4.0")
+          .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"));
+
+  std::ostringstream files_out;
+
+  {
+    std::unique_ptr<XmlElementWriter> filters_group =
+        project.SubElement("ItemGroup");
+    XmlElementWriter files_group(files_out, "ItemGroup", XmlAttributes(), 2);
+
+    // File paths are relative to vcxproj files which are generated to out dirs.
+    // Filters tree structure need to reflect source directories and be relative
+    // to target file. We need two path outputs then.
+    PathOutput file_path_output(
+        GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
+        build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
+    PathOutput filter_path_output(target->label().dir(),
+                                  build_settings_->root_path_utf8(),
+                                  EscapingMode::ESCAPE_NONE);
+
+    std::set<std::string> processed_filters;
+
+    for (const auto& file_and_type : source_types) {
+      std::unique_ptr<XmlElementWriter> cl_item = files_group.SubElement(
+          file_and_type.compile_type, "Include",
+          SourceFileWriter(file_path_output, *file_and_type.file));
+
+      std::ostringstream target_relative_out;
+      filter_path_output.WriteFile(target_relative_out, *file_and_type.file);
+      std::string target_relative_path = target_relative_out.str();
+      ConvertPathToSystem(&target_relative_path);
+      std::string_view filter_path = FindParentDir(&target_relative_path);
+
+      if (!filter_path.empty()) {
+        std::string filter_path_str(filter_path);
+        while (processed_filters.find(filter_path_str) ==
+               processed_filters.end()) {
+          auto it = processed_filters.insert(filter_path_str).first;
+          filters_group
+              ->SubElement("Filter", XmlAttributes("Include", filter_path_str))
+              ->SubElement("UniqueIdentifier")
+              ->Text(MakeGuid(filter_path_str, kGuidSeedFilter));
+          filter_path_str = std::string(FindParentDir(&(*it)));
+          if (filter_path_str.empty())
+            break;
+        }
+        cl_item->SubElement("Filter")->Text(filter_path);
+      }
+    }
+  }
+
+  project.Text(files_out.str());
+}
+
+bool VisualStudioWriter::WriteSolutionFile(const std::string& sln_name,
+                                           Err* err) {
+  std::string name = sln_name.empty() ? "all" : sln_name;
+  SourceFile sln_file = build_settings_->build_dir().ResolveRelativeFile(
+      Value(nullptr, name + ".sln"), err);
+  if (sln_file.is_null())
+    return false;
+
+  base::FilePath sln_path = build_settings_->GetFullPath(sln_file);
+
+  std::stringstream string_out;
+  WriteSolutionFileContents(string_out, sln_path.DirName());
+
+  // Only write the content to the file if it's different. That is
+  // both a performance optimization and more importantly, prevents
+  // Visual Studio from reloading the projects.
+  return WriteFileIfChanged(sln_path, string_out.str(), err);
+}
+
+void VisualStudioWriter::WriteSolutionFileContents(
+    std::ostream& out,
+    const base::FilePath& solution_dir_path) {
+  out << "Microsoft Visual Studio Solution File, Format Version 12.00"
+      << std::endl;
+  out << "# " << version_string_ << std::endl;
+
+  SourceDir solution_dir(FilePathToUTF8(solution_dir_path));
+  for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
+    out << "Project(\"" << kGuidTypeFolder << "\") = \"(" << folder->name
+        << ")\", \"" << RebasePath(folder->path, solution_dir) << "\", \""
+        << folder->guid << "\"" << std::endl;
+    out << "EndProject" << std::endl;
+  }
+
+  for (const std::unique_ptr<SolutionProject>& project : projects_) {
+    out << "Project(\"" << kGuidTypeProject << "\") = \"" << project->name
+        << "\", \"" << RebasePath(project->path, solution_dir) << "\", \""
+        << project->guid << "\"" << std::endl;
+    out << "EndProject" << std::endl;
+  }
+
+  out << "Global" << std::endl;
+
+  out << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"
+      << std::endl;
+  const std::string config_mode_prefix = std::string(kConfigurationName) + '|';
+  const std::string config_mode = config_mode_prefix + config_platform_;
+  out << "\t\t" << config_mode << " = " << config_mode << std::endl;
+  out << "\tEndGlobalSection" << std::endl;
+
+  out << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"
+      << std::endl;
+  for (const std::unique_ptr<SolutionProject>& project : projects_) {
+    const std::string project_config_mode =
+        config_mode_prefix + project->config_platform;
+    out << "\t\t" << project->guid << '.' << config_mode
+        << ".ActiveCfg = " << project_config_mode << std::endl;
+    out << "\t\t" << project->guid << '.' << config_mode
+        << ".Build.0 = " << project_config_mode << std::endl;
+  }
+  out << "\tEndGlobalSection" << std::endl;
+
+  out << "\tGlobalSection(SolutionProperties) = preSolution" << std::endl;
+  out << "\t\tHideSolutionNode = FALSE" << std::endl;
+  out << "\tEndGlobalSection" << std::endl;
+
+  out << "\tGlobalSection(NestedProjects) = preSolution" << std::endl;
+  for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
+    if (folder->parent_folder) {
+      out << "\t\t" << folder->guid << " = " << folder->parent_folder->guid
+          << std::endl;
+    }
+  }
+  for (const std::unique_ptr<SolutionProject>& project : projects_) {
+    out << "\t\t" << project->guid << " = " << project->parent_folder->guid
+        << std::endl;
+  }
+  out << "\tEndGlobalSection" << std::endl;
+
+  out << "EndGlobal" << std::endl;
+}
+
+void VisualStudioWriter::ResolveSolutionFolders() {
+  root_folder_path_.clear();
+
+  // Get all project directories. Create solution folder for each directory.
+  std::map<std::string_view, SolutionEntry*> processed_paths;
+  for (const std::unique_ptr<SolutionProject>& project : projects_) {
+    std::string_view folder_path = project->label_dir_path;
+    if (IsSlash(folder_path[folder_path.size() - 1]))
+      folder_path = folder_path.substr(0, folder_path.size() - 1);
+    auto it = processed_paths.find(folder_path);
+    if (it != processed_paths.end()) {
+      project->parent_folder = it->second;
+    } else {
+      std::string folder_path_str(folder_path);
+      std::unique_ptr<SolutionEntry> folder = std::make_unique<SolutionEntry>(
+          std::string(
+              FindLastDirComponent(SourceDir(std::string(folder_path)))),
+          folder_path_str, MakeGuid(folder_path_str, kGuidSeedFolder));
+      project->parent_folder = folder.get();
+      processed_paths[folder_path] = folder.get();
+      folders_.push_back(std::move(folder));
+
+      if (root_folder_path_.empty()) {
+        root_folder_path_ = folder_path_str;
+      } else {
+        size_t common_prefix_len = 0;
+        size_t max_common_length =
+            std::min(root_folder_path_.size(), folder_path.size());
+        size_t i;
+        for (i = common_prefix_len; i < max_common_length; ++i) {
+          if (IsSlash(root_folder_path_[i]) && IsSlash(folder_path[i]))
+            common_prefix_len = i + 1;
+          else if (root_folder_path_[i] != folder_path[i])
+            break;
+        }
+        if (i == max_common_length &&
+            (i == folder_path.size() || IsSlash(folder_path[i])))
+          common_prefix_len = max_common_length;
+        if (common_prefix_len < root_folder_path_.size()) {
+          if (IsSlash(root_folder_path_[common_prefix_len - 1]))
+            --common_prefix_len;
+          root_folder_path_ = root_folder_path_.substr(0, common_prefix_len);
+        }
+      }
+    }
+  }
+
+  // Create also all parent folders up to |root_folder_path_|.
+  SolutionFolders additional_folders;
+  for (const std::unique_ptr<SolutionEntry>& solution_folder : folders_) {
+    if (solution_folder->path == root_folder_path_)
+      continue;
+
+    SolutionEntry* folder = solution_folder.get();
+    std::string_view parent_path;
+    while ((parent_path = FindParentDir(&folder->path)) != root_folder_path_) {
+      auto it = processed_paths.find(parent_path);
+      if (it != processed_paths.end()) {
+        folder = it->second;
+      } else {
+        std::unique_ptr<SolutionEntry> new_folder =
+            std::make_unique<SolutionEntry>(
+                std::string(
+                    FindLastDirComponent(SourceDir(std::string(parent_path)))),
+                std::string(parent_path),
+                MakeGuid(std::string(parent_path), kGuidSeedFolder));
+        processed_paths[parent_path] = new_folder.get();
+        folder = new_folder.get();
+        additional_folders.push_back(std::move(new_folder));
+      }
+    }
+  }
+  folders_.insert(folders_.end(),
+                  std::make_move_iterator(additional_folders.begin()),
+                  std::make_move_iterator(additional_folders.end()));
+
+  // Sort folders by path.
+  std::sort(folders_.begin(), folders_.end(),
+            [](const std::unique_ptr<SolutionEntry>& a,
+               const std::unique_ptr<SolutionEntry>& b) {
+              return a->path < b->path;
+            });
+
+  // Match subfolders with their parents. Since |folders_| are sorted by path we
+  // know that parent folder always precedes its children in vector.
+  std::vector<SolutionEntry*> parents;
+  for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
+    while (!parents.empty()) {
+      if (base::StartsWith(folder->path, parents.back()->path,
+                           base::CompareCase::SENSITIVE)) {
+        folder->parent_folder = parents.back();
+        break;
+      } else {
+        parents.pop_back();
+      }
+    }
+    parents.push_back(folder.get());
+  }
+}
+
+std::string VisualStudioWriter::GetNinjaTarget(const Target* target) {
+  std::ostringstream ninja_target_out;
+  DCHECK(!target->dependency_output_file().value().empty());
+  ninja_path_output_.WriteFile(ninja_target_out,
+                               target->dependency_output_file());
+  std::string s = ninja_target_out.str();
+  if (s.compare(0, 2, "./") == 0)
+    s = s.substr(2);
+  return s;
+}
diff --git a/src/gn/visual_studio_writer.h b/src/gn/visual_studio_writer.h
new file mode 100644 (file)
index 0000000..e4957a1
--- /dev/null
@@ -0,0 +1,168 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_VISUAL_STUDIO_WRITER_H_
+#define TOOLS_GN_VISUAL_STUDIO_WRITER_H_
+
+#include <iosfwd>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "gn/path_output.h"
+
+namespace base {
+class FilePath;
+}
+
+class Builder;
+class BuildSettings;
+class Err;
+class SourceFile;
+class Target;
+
+class VisualStudioWriter {
+ public:
+  enum Version {
+    Vs2013 = 1,  // Visual Studio 2013
+    Vs2015,      // Visual Studio 2015
+    Vs2017,      // Visual Studio 2017
+    Vs2019,      // Visual Studio 2019
+  };
+
+  // Writes Visual Studio project and solution files. |sln_name| is the optional
+  // solution file name ("all" is used if not specified). |filters| is optional
+  // semicolon-separated list of label patterns used to limit the set of
+  // generated projects. Only matching targets and their dependencies (unless
+  // |no_deps| is true) will be included to the solution. On failure will
+  // populate |err| and will return false. |win_sdk| is the Windows SDK version
+  // which will be used by Visual Studio IntelliSense.
+  static bool RunAndWriteFiles(const BuildSettings* build_settings,
+                               const Builder& builder,
+                               Version version,
+                               const std::string& sln_name,
+                               const std::string& filters,
+                               const std::string& win_sdk,
+                               const std::string& ninja_extra_args,
+                               bool no_deps,
+                               Err* err);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest, ResolveSolutionFolders);
+  FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest,
+                           ResolveSolutionFolders_AbsPath);
+  FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest, NoDotSlash);
+
+  // Solution project or folder.
+  struct SolutionEntry {
+    SolutionEntry(const std::string& name,
+                  const std::string& path,
+                  const std::string& guid);
+    virtual ~SolutionEntry();
+
+    // Entry name. For projects must be unique in the solution.
+    std::string name;
+    // Absolute project file or folder directory path.
+    std::string path;
+    // GUID-like string.
+    std::string guid;
+    // Pointer to parent folder. nullptr if entry has no parent.
+    SolutionEntry* parent_folder;
+  };
+
+  struct SolutionProject : public SolutionEntry {
+    SolutionProject(const std::string& name,
+                    const std::string& path,
+                    const std::string& guid,
+                    const std::string& label_dir_path,
+                    const std::string& config_platform);
+    ~SolutionProject() override;
+
+    // Absolute label dir path.
+    std::string label_dir_path;
+    // Configuration platform. May be different than solution config platform.
+    std::string config_platform;
+  };
+
+  struct SourceFileCompileTypePair {
+    SourceFileCompileTypePair(const SourceFile* file, const char* compile_type);
+    ~SourceFileCompileTypePair();
+
+    // Source file.
+    const SourceFile* file;
+    // Compile type string.
+    const char* compile_type;
+  };
+
+  using SolutionProjects = std::vector<std::unique_ptr<SolutionProject>>;
+  using SolutionFolders = std::vector<std::unique_ptr<SolutionEntry>>;
+  using SourceFileCompileTypePairs = std::vector<SourceFileCompileTypePair>;
+
+  VisualStudioWriter(const BuildSettings* build_settings,
+                     const char* config_platform,
+                     Version version,
+                     const std::string& win_kit);
+  ~VisualStudioWriter();
+
+  bool WriteProjectFiles(const Target* target,
+                         const std::string& ninja_extra_args,
+                         Err* err);
+  bool WriteProjectFileContents(std::ostream& out,
+                                const SolutionProject& solution_project,
+                                const Target* target,
+                                const std::string& ninja_extra_args,
+                                SourceFileCompileTypePairs* source_types,
+                                Err* err);
+  void WriteFiltersFileContents(std::ostream& out,
+                                const Target* target,
+                                const SourceFileCompileTypePairs& source_types);
+  bool WriteSolutionFile(const std::string& sln_name, Err* err);
+  void WriteSolutionFileContents(std::ostream& out,
+                                 const base::FilePath& solution_dir_path);
+
+  // Resolves all solution folders (parent folders for projects) into |folders_|
+  // and updates |root_folder_dir_|. Also sets |parent_folder| for |projects_|.
+  void ResolveSolutionFolders();
+
+  std::string GetNinjaTarget(const Target* target);
+
+  const BuildSettings* build_settings_;
+
+  // Toolset version.
+  const char* toolset_version_;
+
+  // Project version.
+  const char* project_version_;
+
+  // Visual Studio version string.
+  const char* version_string_;
+
+  // Platform for solution configuration (Win32, x64). Some projects may be
+  // configured for different platform.
+  const char* config_platform_;
+
+  // All projects contained by solution.
+  SolutionProjects projects_;
+
+  // Absolute root solution folder path.
+  std::string root_folder_path_;
+
+  // Folders for all solution projects.
+  SolutionFolders folders_;
+
+  // Semicolon-separated Windows SDK include directories.
+  std::string windows_kits_include_dirs_;
+
+  // Path formatter for ninja targets.
+  PathOutput ninja_path_output_;
+
+  // Windows 10 SDK version string (e.g. 10.0.14393.0)
+  std::string windows_sdk_version_;
+
+  DISALLOW_COPY_AND_ASSIGN(VisualStudioWriter);
+};
+
+#endif  // TOOLS_GN_VISUAL_STUDIO_WRITER_H_
diff --git a/src/gn/visual_studio_writer_unittest.cc b/src/gn/visual_studio_writer_unittest.cc
new file mode 100644 (file)
index 0000000..5e89fe1
--- /dev/null
@@ -0,0 +1,200 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/visual_studio_writer.h"
+
+#include <memory>
+
+#include "base/strings/string_util.h"
+#include "gn/test_with_scope.h"
+#include "gn/visual_studio_utils.h"
+#include "util/test/test.h"
+
+namespace {
+
+class VisualStudioWriterTest : public testing::Test {
+ protected:
+  TestWithScope setup_;
+};
+
+std::string MakeTestPath(const std::string& path) {
+#if defined(OS_WIN)
+  return "C:" + path;
+#else
+  return path;
+#endif
+}
+
+}  // namespace
+
+TEST_F(VisualStudioWriterTest, ResolveSolutionFolders) {
+  VisualStudioWriter writer(setup_.build_settings(), "Win32",
+                            VisualStudioWriter::Version::Vs2015,
+                            "10.0.17134.0");
+
+  std::string path =
+      MakeTestPath("/foo/chromium/src/out/Debug/obj/base/base.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "base", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/base"), "Win32"));
+
+  path = MakeTestPath("/foo/chromium/src/out/Debug/obj/tools/gn/gn.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "gn", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/tools/gn"), "Win32"));
+
+  path = MakeTestPath("/foo/chromium/src/out/Debug/obj/chrome/chrome.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "chrome", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/chrome"), "Win32"));
+
+  path = MakeTestPath("/foo/chromium/src/out/Debug/obj/base/bar.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "bar", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/base"), "Win32"));
+
+  writer.ResolveSolutionFolders();
+
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src"), writer.root_folder_path_);
+
+  ASSERT_EQ(4u, writer.folders_.size());
+
+  ASSERT_EQ("base", writer.folders_[0]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/base"), writer.folders_[0]->path);
+  ASSERT_EQ(nullptr, writer.folders_[0]->parent_folder);
+
+  ASSERT_EQ("chrome", writer.folders_[1]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/chrome"), writer.folders_[1]->path);
+  ASSERT_EQ(nullptr, writer.folders_[1]->parent_folder);
+
+  ASSERT_EQ("tools", writer.folders_[2]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/tools"), writer.folders_[2]->path);
+  ASSERT_EQ(nullptr, writer.folders_[2]->parent_folder);
+
+  ASSERT_EQ("gn", writer.folders_[3]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/tools/gn"),
+            writer.folders_[3]->path);
+  ASSERT_EQ(writer.folders_[2].get(), writer.folders_[3]->parent_folder);
+
+  ASSERT_EQ(writer.folders_[0].get(), writer.projects_[0]->parent_folder);
+  ASSERT_EQ(writer.folders_[3].get(), writer.projects_[1]->parent_folder);
+  ASSERT_EQ(writer.folders_[1].get(), writer.projects_[2]->parent_folder);
+  ASSERT_EQ(writer.folders_[0].get(), writer.projects_[3]->parent_folder);
+}
+
+TEST_F(VisualStudioWriterTest, ResolveSolutionFolders_AbsPath) {
+  VisualStudioWriter writer(setup_.build_settings(), "Win32",
+                            VisualStudioWriter::Version::Vs2015,
+                            "10.0.17134.0");
+
+  std::string path =
+      MakeTestPath("/foo/chromium/src/out/Debug/obj/base/base.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "base", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/base"), "Win32"));
+
+  path = MakeTestPath("/foo/chromium/src/out/Debug/obj/tools/gn/gn.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "gn", path, MakeGuid(path, "project"),
+          MakeTestPath("/foo/chromium/src/tools/gn"), "Win32"));
+
+  path = MakeTestPath(
+      "/foo/chromium/src/out/Debug/obj/ABS_PATH/C/foo/bar/bar.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "bar", path, MakeGuid(path, "project"), MakeTestPath("/foo/bar"),
+          "Win32"));
+
+  std::string baz_label_dir_path = MakeTestPath("/foo/bar/baz");
+#if defined(OS_WIN)
+  // Make sure mixed lower and upper-case drive letters are handled properly.
+  baz_label_dir_path[0] = base::ToLowerASCII(baz_label_dir_path[0]);
+#endif
+  path = MakeTestPath(
+      "/foo/chromium/src/out/Debug/obj/ABS_PATH/C/foo/bar/baz/baz.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "baz", path, MakeGuid(path, "project"), baz_label_dir_path, "Win32"));
+
+  writer.ResolveSolutionFolders();
+
+  ASSERT_EQ(MakeTestPath("/foo"), writer.root_folder_path_);
+
+  ASSERT_EQ(7u, writer.folders_.size());
+
+  ASSERT_EQ("bar", writer.folders_[0]->name);
+  ASSERT_EQ(MakeTestPath("/foo/bar"), writer.folders_[0]->path);
+  ASSERT_EQ(nullptr, writer.folders_[0]->parent_folder);
+
+  ASSERT_EQ("baz", writer.folders_[1]->name);
+  ASSERT_EQ(MakeTestPath("/foo/bar/baz"), writer.folders_[1]->path);
+  ASSERT_EQ(writer.folders_[0].get(), writer.folders_[1]->parent_folder);
+
+  ASSERT_EQ("chromium", writer.folders_[2]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium"), writer.folders_[2]->path);
+  ASSERT_EQ(nullptr, writer.folders_[2]->parent_folder);
+
+  ASSERT_EQ("src", writer.folders_[3]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src"), writer.folders_[3]->path);
+  ASSERT_EQ(writer.folders_[2].get(), writer.folders_[3]->parent_folder);
+
+  ASSERT_EQ("base", writer.folders_[4]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/base"), writer.folders_[4]->path);
+  ASSERT_EQ(writer.folders_[3].get(), writer.folders_[4]->parent_folder);
+
+  ASSERT_EQ("tools", writer.folders_[5]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/tools"), writer.folders_[5]->path);
+  ASSERT_EQ(writer.folders_[3].get(), writer.folders_[5]->parent_folder);
+
+  ASSERT_EQ("gn", writer.folders_[6]->name);
+  ASSERT_EQ(MakeTestPath("/foo/chromium/src/tools/gn"),
+            writer.folders_[6]->path);
+  ASSERT_EQ(writer.folders_[5].get(), writer.folders_[6]->parent_folder);
+
+  ASSERT_EQ(writer.folders_[4].get(), writer.projects_[0]->parent_folder);
+  ASSERT_EQ(writer.folders_[6].get(), writer.projects_[1]->parent_folder);
+  ASSERT_EQ(writer.folders_[0].get(), writer.projects_[2]->parent_folder);
+  ASSERT_EQ(writer.folders_[1].get(), writer.projects_[3]->parent_folder);
+}
+
+TEST_F(VisualStudioWriterTest, NoDotSlash) {
+  VisualStudioWriter writer(setup_.build_settings(), "Win32",
+                            VisualStudioWriter::Version::Vs2015,
+                            "10.0.17134.0");
+
+  std::string path = MakeTestPath("blah.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "base", path, MakeGuid(path, "project"), MakeTestPath("/foo"),
+          "Win32"));
+
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolAlink);
+  tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}", ""));
+
+  Toolchain toolchain(setup_.settings(), Label(SourceDir("//tc/"), "tc"));
+  toolchain.SetTool(std::move(tool));
+
+  Target target(setup_.settings(), Label(SourceDir("//baz/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(&toolchain);
+
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  VisualStudioWriter::SourceFileCompileTypePairs source_types;
+
+  std::stringstream file_contents;
+  writer.WriteProjectFileContents(file_contents, *writer.projects_.back(),
+                                  &target, "", &source_types, &err);
+
+  // Should find args of a ninja clean command, with no ./ before the file name.
+  ASSERT_NE(file_contents.str().find("-tclean baz"), std::string::npos);
+}
diff --git a/src/gn/xcode_object.cc b/src/gn/xcode_object.cc
new file mode 100644 (file)
index 0000000..0761b4f
--- /dev/null
@@ -0,0 +1,1118 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/xcode_object.h"
+
+#include <iomanip>
+#include <iterator>
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
+
+// Helper methods -------------------------------------------------------------
+
+namespace {
+struct IndentRules {
+  bool one_line;
+  unsigned level;
+};
+
+std::vector<std::unique_ptr<PBXObject>> EmptyPBXObjectVector() {
+  return std::vector<std::unique_ptr<PBXObject>>();
+}
+
+bool CharNeedEscaping(char c) {
+  if (base::IsAsciiAlpha(c) || base::IsAsciiDigit(c))
+    return false;
+  if (c == '$' || c == '.' || c == '/' || c == '_')
+    return false;
+  return true;
+}
+
+bool StringNeedEscaping(const std::string& string) {
+  if (string.empty())
+    return true;
+  if (string.find("___") != std::string::npos)
+    return true;
+
+  for (char c : string) {
+    if (CharNeedEscaping(c))
+      return true;
+  }
+  return false;
+}
+
+std::string EncodeString(const std::string& string) {
+  if (!StringNeedEscaping(string))
+    return string;
+
+  std::stringstream buffer;
+  buffer << '"';
+  for (char c : string) {
+    if (c <= 31) {
+      switch (c) {
+        case '\a':
+          buffer << "\\a";
+          break;
+        case '\b':
+          buffer << "\\b";
+          break;
+        case '\t':
+          buffer << "\\t";
+          break;
+        case '\n':
+        case '\r':
+          buffer << "\\n";
+          break;
+        case '\v':
+          buffer << "\\v";
+          break;
+        case '\f':
+          buffer << "\\f";
+          break;
+        default:
+          buffer << std::hex << std::setw(4) << std::left << "\\U"
+                 << static_cast<unsigned>(c);
+          break;
+      }
+    } else {
+      if (c == '"' || c == '\\')
+        buffer << '\\';
+      buffer << c;
+    }
+  }
+  buffer << '"';
+  return buffer.str();
+}
+
+struct SourceTypeForExt {
+  const char* ext;
+  const char* source_type;
+};
+
+const SourceTypeForExt kSourceTypeForExt[] = {
+    {"a", "archive.ar"},
+    {"app", "wrapper.application"},
+    {"appex", "wrapper.app-extension"},
+    {"bdic", "file"},
+    {"bundle", "wrapper.cfbundle"},
+    {"c", "sourcecode.c.c"},
+    {"cc", "sourcecode.cpp.cpp"},
+    {"cpp", "sourcecode.cpp.cpp"},
+    {"css", "text.css"},
+    {"cxx", "sourcecode.cpp.cpp"},
+    {"dart", "sourcecode"},
+    {"dylib", "compiled.mach-o.dylib"},
+    {"framework", "wrapper.framework"},
+    {"h", "sourcecode.c.h"},
+    {"hxx", "sourcecode.cpp.h"},
+    {"icns", "image.icns"},
+    {"java", "sourcecode.java"},
+    {"js", "sourcecode.javascript"},
+    {"kext", "wrapper.kext"},
+    {"m", "sourcecode.c.objc"},
+    {"mm", "sourcecode.cpp.objcpp"},
+    {"nib", "wrapper.nib"},
+    {"o", "compiled.mach-o.objfile"},
+    {"pdf", "image.pdf"},
+    {"pl", "text.script.perl"},
+    {"plist", "text.plist.xml"},
+    {"pm", "text.script.perl"},
+    {"png", "image.png"},
+    {"py", "text.script.python"},
+    {"r", "sourcecode.rez"},
+    {"rez", "sourcecode.rez"},
+    {"s", "sourcecode.asm"},
+    {"storyboard", "file.storyboard"},
+    {"strings", "text.plist.strings"},
+    {"swift", "sourcecode.swift"},
+    {"ttf", "file"},
+    {"xcassets", "folder.assetcatalog"},
+    {"xcconfig", "text.xcconfig"},
+    {"xcdatamodel", "wrapper.xcdatamodel"},
+    {"xcdatamodeld", "wrapper.xcdatamodeld"},
+    {"xctest", "wrapper.cfbundle"},
+    {"xpc", "wrapper.xpc-service"},
+    {"xib", "file.xib"},
+    {"y", "sourcecode.yacc"},
+};
+
+const char* GetSourceType(const std::string_view& ext) {
+  for (size_t i = 0; i < std::size(kSourceTypeForExt); ++i) {
+    if (kSourceTypeForExt[i].ext == ext)
+      return kSourceTypeForExt[i].source_type;
+  }
+
+  return "text";
+}
+
+bool HasExplicitFileType(const std::string_view& ext) {
+  return ext == "dart";
+}
+
+bool IsSourceFileForIndexing(const std::string_view& ext) {
+  return ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx" ||
+         ext == "m" || ext == "mm";
+}
+
+// Wrapper around a const PBXObject* allowing to print just the object
+// identifier instead of a reference (i.e. identitifer and name). This
+// is used in a few place where Xcode uses the short identifier only.
+struct NoReference {
+  const PBXObject* value;
+
+  explicit NoReference(const PBXObject* value) : value(value) {}
+};
+
+void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
+  out << value;
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, const char* value) {
+  out << EncodeString(value);
+}
+
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::string& value) {
+  out << EncodeString(value);
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, const NoReference& obj) {
+  out << obj.value->id();
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
+  out << value->Reference();
+}
+
+template <typename ObjectClass>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::unique_ptr<ObjectClass>& value) {
+  PrintValue(out, rules, value.get());
+}
+
+template <typename ValueType>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::vector<ValueType>& values) {
+  IndentRules sub_rule{rules.one_line, rules.level + 1};
+  out << "(" << (rules.one_line ? " " : "\n");
+  for (const auto& value : values) {
+    if (!sub_rule.one_line)
+      out << std::string(sub_rule.level, '\t');
+
+    PrintValue(out, sub_rule, value);
+    out << "," << (rules.one_line ? " " : "\n");
+  }
+
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+  out << ")";
+}
+
+template <typename ValueType>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::map<std::string, ValueType>& values) {
+  IndentRules sub_rule{rules.one_line, rules.level + 1};
+  out << "{" << (rules.one_line ? " " : "\n");
+  for (const auto& pair : values) {
+    if (!sub_rule.one_line)
+      out << std::string(sub_rule.level, '\t');
+
+    out << pair.first << " = ";
+    PrintValue(out, sub_rule, pair.second);
+    out << ";" << (rules.one_line ? " " : "\n");
+  }
+
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+  out << "}";
+}
+
+template <typename ValueType>
+void PrintProperty(std::ostream& out,
+                   IndentRules rules,
+                   const char* name,
+                   ValueType&& value) {
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+
+  out << name << " = ";
+  PrintValue(out, rules, std::forward<ValueType>(value));
+  out << ";" << (rules.one_line ? " " : "\n");
+}
+
+struct PBXGroupComparator {
+  using PBXObjectPtr = std::unique_ptr<PBXObject>;
+  bool operator()(const PBXObjectPtr& lhs, const PBXObjectPtr& rhs) {
+    if (lhs->Class() != rhs->Class())
+      return rhs->Class() < lhs->Class();
+
+    if (lhs->Class() == PBXGroupClass) {
+      PBXGroup* lhs_group = static_cast<PBXGroup*>(lhs.get());
+      PBXGroup* rhs_group = static_cast<PBXGroup*>(rhs.get());
+      return lhs_group->name() < rhs_group->name();
+    }
+
+    DCHECK_EQ(lhs->Class(), PBXFileReferenceClass);
+    PBXFileReference* lhs_file = static_cast<PBXFileReference*>(lhs.get());
+    PBXFileReference* rhs_file = static_cast<PBXFileReference*>(rhs.get());
+    return lhs_file->Name() < rhs_file->Name();
+  }
+};
+}  // namespace
+
+// PBXObjectClass -------------------------------------------------------------
+
+const char* ToString(PBXObjectClass cls) {
+  switch (cls) {
+    case PBXAggregateTargetClass:
+      return "PBXAggregateTarget";
+    case PBXBuildFileClass:
+      return "PBXBuildFile";
+    case PBXContainerItemProxyClass:
+      return "PBXContainerItemProxy";
+    case PBXFileReferenceClass:
+      return "PBXFileReference";
+    case PBXFrameworksBuildPhaseClass:
+      return "PBXFrameworksBuildPhase";
+    case PBXGroupClass:
+      return "PBXGroup";
+    case PBXNativeTargetClass:
+      return "PBXNativeTarget";
+    case PBXProjectClass:
+      return "PBXProject";
+    case PBXResourcesBuildPhaseClass:
+      return "PBXResourcesBuildPhase";
+    case PBXShellScriptBuildPhaseClass:
+      return "PBXShellScriptBuildPhase";
+    case PBXSourcesBuildPhaseClass:
+      return "PBXSourcesBuildPhase";
+    case PBXTargetDependencyClass:
+      return "PBXTargetDependency";
+    case XCBuildConfigurationClass:
+      return "XCBuildConfiguration";
+    case XCConfigurationListClass:
+      return "XCConfigurationList";
+  }
+  NOTREACHED();
+  return nullptr;
+}
+
+// PBXObjectVisitor -----------------------------------------------------------
+
+PBXObjectVisitor::PBXObjectVisitor() = default;
+
+PBXObjectVisitor::~PBXObjectVisitor() = default;
+
+// PBXObjectVisitorConst ------------------------------------------------------
+
+PBXObjectVisitorConst::PBXObjectVisitorConst() = default;
+
+PBXObjectVisitorConst::~PBXObjectVisitorConst() = default;
+
+// PBXObject ------------------------------------------------------------------
+
+PBXObject::PBXObject() = default;
+
+PBXObject::~PBXObject() = default;
+
+void PBXObject::SetId(const std::string& id) {
+  DCHECK(id_.empty());
+  DCHECK(!id.empty());
+  id_.assign(id);
+}
+
+std::string PBXObject::Reference() const {
+  std::string comment = Comment();
+  if (comment.empty())
+    return id_;
+
+  return id_ + " /* " + comment + " */";
+}
+
+std::string PBXObject::Comment() const {
+  return Name();
+}
+
+void PBXObject::Visit(PBXObjectVisitor& visitor) {
+  visitor.Visit(this);
+}
+
+void PBXObject::Visit(PBXObjectVisitorConst& visitor) const {
+  visitor.Visit(this);
+}
+
+// PBXBuildPhase --------------------------------------------------------------
+
+PBXBuildPhase::PBXBuildPhase() = default;
+
+PBXBuildPhase::~PBXBuildPhase() = default;
+
+void PBXBuildPhase::AddBuildFile(std::unique_ptr<PBXBuildFile> build_file) {
+  DCHECK(build_file);
+  files_.push_back(std::move(build_file));
+}
+
+void PBXBuildPhase::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  for (const auto& file : files_) {
+    file->Visit(visitor);
+  }
+}
+
+void PBXBuildPhase::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  for (const auto& file : files_) {
+    file->Visit(visitor);
+  }
+}
+
+// PBXTarget ------------------------------------------------------------------
+
+PBXTarget::PBXTarget(const std::string& name,
+                     const std::string& shell_script,
+                     const std::string& config_name,
+                     const PBXAttributes& attributes)
+    : configurations_(
+          std::make_unique<XCConfigurationList>(config_name, attributes, this)),
+      name_(name) {
+  if (!shell_script.empty()) {
+    build_phases_.push_back(
+        std::make_unique<PBXShellScriptBuildPhase>(name, shell_script));
+  }
+}
+
+PBXTarget::~PBXTarget() = default;
+
+void PBXTarget::AddDependency(std::unique_ptr<PBXTargetDependency> dependency) {
+  DCHECK(dependency);
+  dependencies_.push_back(std::move(dependency));
+}
+
+std::string PBXTarget::Name() const {
+  return name_;
+}
+
+void PBXTarget::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  for (const auto& dependency : dependencies_)
+    dependency->Visit(visitor);
+  for (const auto& build_phase : build_phases_)
+    build_phase->Visit(visitor);
+}
+
+void PBXTarget::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  for (const auto& dependency : dependencies_)
+    dependency->Visit(visitor);
+  for (const auto& build_phase : build_phases_)
+    build_phase->Visit(visitor);
+}
+
+// PBXAggregateTarget ---------------------------------------------------------
+
+PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
+                                       const std::string& shell_script,
+                                       const std::string& config_name,
+                                       const PBXAttributes& attributes)
+    : PBXTarget(name, shell_script, config_name, attributes) {}
+
+PBXAggregateTarget::~PBXAggregateTarget() = default;
+
+PBXObjectClass PBXAggregateTarget::Class() const {
+  return PBXAggregateTargetClass;
+}
+
+void PBXAggregateTarget::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "buildPhases", build_phases_);
+  PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "productName", name_);
+  out << indent_str << "};\n";
+}
+
+// PBXBuildFile ---------------------------------------------------------------
+
+PBXBuildFile::PBXBuildFile(const PBXFileReference* file_reference,
+                           const PBXBuildPhase* build_phase)
+    : file_reference_(file_reference), build_phase_(build_phase) {
+  DCHECK(file_reference_);
+  DCHECK(build_phase_);
+}
+
+PBXBuildFile::~PBXBuildFile() = default;
+
+PBXObjectClass PBXBuildFile::Class() const {
+  return PBXBuildFileClass;
+}
+
+std::string PBXBuildFile::Name() const {
+  return file_reference_->Name() + " in " + build_phase_->Name();
+}
+
+void PBXBuildFile::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {true, 0};
+  out << indent_str << Reference() << " = {";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "fileRef", file_reference_);
+  out << "};\n";
+}
+
+// PBXContainerItemProxy ------------------------------------------------------
+PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project,
+                                             const PBXTarget* target)
+    : project_(project), target_(target) {}
+
+PBXContainerItemProxy::~PBXContainerItemProxy() = default;
+
+PBXObjectClass PBXContainerItemProxy::Class() const {
+  return PBXContainerItemProxyClass;
+}
+
+std::string PBXContainerItemProxy::Name() const {
+  return "PBXContainerItemProxy";
+}
+
+void PBXContainerItemProxy::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "containerPortal", project_);
+  PrintProperty(out, rules, "proxyType", 1u);
+  PrintProperty(out, rules, "remoteGlobalIDString", NoReference(target_));
+  PrintProperty(out, rules, "remoteInfo", target_->Name());
+  out << indent_str << "};\n";
+}
+
+// PBXFileReference -----------------------------------------------------------
+
+PBXFileReference::PBXFileReference(const std::string& name,
+                                   const std::string& path,
+                                   const std::string& type)
+    : name_(name), path_(path), type_(type) {}
+
+PBXFileReference::~PBXFileReference() = default;
+
+PBXObjectClass PBXFileReference::Class() const {
+  return PBXFileReferenceClass;
+}
+
+std::string PBXFileReference::Name() const {
+  return name_;
+}
+
+std::string PBXFileReference::Comment() const {
+  return !name_.empty() ? name_ : path_;
+}
+
+void PBXFileReference::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {true, 0};
+  out << indent_str << Reference() << " = {";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+
+  if (!type_.empty()) {
+    PrintProperty(out, rules, "explicitFileType", type_);
+    PrintProperty(out, rules, "includeInIndex", 0u);
+  } else {
+    std::string_view ext = FindExtension(&name_);
+    if (HasExplicitFileType(ext))
+      PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
+    else
+      PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
+  }
+
+  if (!name_.empty() && name_ != path_)
+    PrintProperty(out, rules, "name", name_);
+
+  DCHECK(!path_.empty());
+  PrintProperty(out, rules, "path", path_);
+  PrintProperty(out, rules, "sourceTree",
+                type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
+  out << "};\n";
+}
+
+// PBXFrameworksBuildPhase ----------------------------------------------------
+
+PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() = default;
+
+PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() = default;
+
+PBXObjectClass PBXFrameworksBuildPhase::Class() const {
+  return PBXFrameworksBuildPhaseClass;
+}
+
+std::string PBXFrameworksBuildPhase::Name() const {
+  return "Frameworks";
+}
+
+void PBXFrameworksBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", files_);
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  out << indent_str << "};\n";
+}
+
+// PBXGroup -------------------------------------------------------------------
+
+PBXGroup::PBXGroup(const std::string& path, const std::string& name)
+    : name_(name), path_(path) {}
+
+PBXGroup::~PBXGroup() = default;
+
+PBXFileReference* PBXGroup::AddSourceFile(const std::string& navigator_path,
+                                          const std::string& source_path) {
+  DCHECK(!navigator_path.empty());
+  DCHECK(!source_path.empty());
+  std::string::size_type sep = navigator_path.find("/");
+  if (sep == std::string::npos) {
+    // Prevent same file reference being created and added multiple times.
+    for (const auto& child : children_) {
+      if (child->Class() != PBXFileReferenceClass)
+        continue;
+
+      PBXFileReference* child_as_file_reference =
+          static_cast<PBXFileReference*>(child.get());
+      if (child_as_file_reference->Name() == navigator_path &&
+          child_as_file_reference->path() == source_path) {
+        return child_as_file_reference;
+      }
+    }
+
+    return CreateChild<PBXFileReference>(navigator_path, source_path,
+                                         std::string());
+  }
+
+  PBXGroup* group = nullptr;
+  std::string_view component(navigator_path.data(), sep);
+  for (const auto& child : children_) {
+    if (child->Class() != PBXGroupClass)
+      continue;
+
+    PBXGroup* child_as_group = static_cast<PBXGroup*>(child.get());
+    if (child_as_group->name_ == component) {
+      group = child_as_group;
+      break;
+    }
+  }
+
+  if (!group) {
+    group =
+        CreateChild<PBXGroup>(std::string(component), std::string(component));
+  }
+
+  DCHECK(group);
+  DCHECK(group->name_ == component);
+  return group->AddSourceFile(navigator_path.substr(sep + 1), source_path);
+}
+
+PBXObjectClass PBXGroup::Class() const {
+  return PBXGroupClass;
+}
+
+std::string PBXGroup::Name() const {
+  if (!name_.empty())
+    return name_;
+  if (!path_.empty())
+    return path_;
+  return std::string();
+}
+
+void PBXGroup::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  for (const auto& child : children_) {
+    child->Visit(visitor);
+  }
+}
+
+void PBXGroup::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  for (const auto& child : children_) {
+    child->Visit(visitor);
+  }
+}
+
+void PBXGroup::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "children", children_);
+  if (!name_.empty())
+    PrintProperty(out, rules, "name", name_);
+  if (is_source_ && !path_.empty())
+    PrintProperty(out, rules, "path", path_);
+  PrintProperty(out, rules, "sourceTree", "<group>");
+  out << indent_str << "};\n";
+}
+
+PBXObject* PBXGroup::AddChildImpl(std::unique_ptr<PBXObject> child) {
+  DCHECK(child);
+  DCHECK(child->Class() == PBXGroupClass ||
+         child->Class() == PBXFileReferenceClass);
+
+  PBXObject* child_ptr = child.get();
+  if (autosorted()) {
+    auto iter = std::lower_bound(children_.begin(), children_.end(), child,
+                                 PBXGroupComparator());
+    children_.insert(iter, std::move(child));
+  } else {
+    children_.push_back(std::move(child));
+  }
+  return child_ptr;
+}
+
+// PBXNativeTarget ------------------------------------------------------------
+
+PBXNativeTarget::PBXNativeTarget(const std::string& name,
+                                 const std::string& shell_script,
+                                 const std::string& config_name,
+                                 const PBXAttributes& attributes,
+                                 const std::string& product_type,
+                                 const std::string& product_name,
+                                 const PBXFileReference* product_reference)
+    : PBXTarget(name, shell_script, config_name, attributes),
+      product_reference_(product_reference),
+      product_type_(product_type),
+      product_name_(product_name) {
+  DCHECK(product_reference_);
+  build_phases_.push_back(std::make_unique<PBXSourcesBuildPhase>());
+  source_build_phase_ =
+      static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
+
+  build_phases_.push_back(std::make_unique<PBXFrameworksBuildPhase>());
+  build_phases_.push_back(std::make_unique<PBXResourcesBuildPhase>());
+  resource_build_phase_ =
+      static_cast<PBXResourcesBuildPhase*>(build_phases_.back().get());
+}
+
+PBXNativeTarget::~PBXNativeTarget() = default;
+
+void PBXNativeTarget::AddResourceFile(const PBXFileReference* file_reference) {
+  DCHECK(file_reference);
+  resource_build_phase_->AddBuildFile(
+      std::make_unique<PBXBuildFile>(file_reference, resource_build_phase_));
+}
+
+void PBXNativeTarget::AddFileForIndexing(
+    const PBXFileReference* file_reference) {
+  DCHECK(file_reference);
+  source_build_phase_->AddBuildFile(
+      std::make_unique<PBXBuildFile>(file_reference, source_build_phase_));
+}
+
+PBXObjectClass PBXNativeTarget::Class() const {
+  return PBXNativeTargetClass;
+}
+
+void PBXNativeTarget::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "buildPhases", build_phases_);
+  PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "dependencies", dependencies_);
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "productName", product_name_);
+  PrintProperty(out, rules, "productReference", product_reference_);
+  PrintProperty(out, rules, "productType", product_type_);
+  out << indent_str << "};\n";
+}
+
+// PBXProject -----------------------------------------------------------------
+
+PBXProject::PBXProject(const std::string& name,
+                       const std::string& config_name,
+                       const std::string& source_path,
+                       const PBXAttributes& attributes)
+    : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
+  main_group_ = std::make_unique<PBXGroup>();
+  main_group_->set_autosorted(false);
+
+  sources_ = main_group_->CreateChild<PBXGroup>(source_path, "Source");
+  sources_->set_is_source(true);
+
+  products_ = main_group_->CreateChild<PBXGroup>(std::string(), "Products");
+
+  configurations_ =
+      std::make_unique<XCConfigurationList>(config_name, attributes, this);
+}
+
+PBXProject::~PBXProject() = default;
+
+void PBXProject::AddSourceFileToIndexingTarget(
+    const std::string& navigator_path,
+    const std::string& source_path) {
+  if (!target_for_indexing_) {
+    AddIndexingTarget();
+  }
+  AddSourceFile(navigator_path, source_path, target_for_indexing_);
+}
+
+void PBXProject::AddSourceFile(const std::string& navigator_path,
+                               const std::string& source_path,
+                               PBXNativeTarget* target) {
+  PBXFileReference* file_reference =
+      sources_->AddSourceFile(navigator_path, source_path);
+  std::string_view ext = FindExtension(&source_path);
+  if (!IsSourceFileForIndexing(ext))
+    return;
+
+  DCHECK(target);
+  target->AddFileForIndexing(file_reference);
+}
+
+void PBXProject::AddAggregateTarget(const std::string& name,
+                                    const std::string& shell_script) {
+  PBXAttributes attributes;
+  attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
+  attributes["CODE_SIGNING_REQUIRED"] = "NO";
+  attributes["CONFIGURATION_BUILD_DIR"] = ".";
+  attributes["PRODUCT_NAME"] = name;
+
+  targets_.push_back(std::make_unique<PBXAggregateTarget>(
+      name, shell_script, config_name_, attributes));
+}
+
+void PBXProject::AddIndexingTarget() {
+  DCHECK(!target_for_indexing_);
+  PBXAttributes attributes;
+  attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
+  attributes["CODE_SIGNING_REQUIRED"] = "NO";
+  attributes["EXECUTABLE_PREFIX"] = "";
+  attributes["HEADER_SEARCH_PATHS"] = sources_->path();
+  attributes["PRODUCT_NAME"] = "sources";
+
+  PBXFileReference* product_reference =
+      products_->CreateChild<PBXFileReference>(std::string(), "sources",
+                                               "compiled.mach-o.executable");
+
+  const char product_type[] = "com.apple.product-type.tool";
+  targets_.push_back(std::make_unique<PBXNativeTarget>(
+      "sources", std::string(), config_name_, attributes, product_type,
+      "sources", product_reference));
+  target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
+}
+
+PBXNativeTarget* PBXProject::AddNativeTarget(
+    const std::string& name,
+    const std::string& type,
+    const std::string& output_name,
+    const std::string& output_type,
+    const std::string& output_dir,
+    const std::string& shell_script,
+    const PBXAttributes& extra_attributes) {
+  std::string_view ext = FindExtension(&output_name);
+  PBXFileReference* product = products_->CreateChild<PBXFileReference>(
+      std::string(), output_name, type.empty() ? GetSourceType(ext) : type);
+
+  // Per Xcode build settings documentation: Product Name (PRODUCT_NAME) should
+  // the basename of the product generated by the target.
+  // Therefore, take the basename of output name without file extension as the
+  // "PRODUCT_NAME".
+  size_t basename_offset = FindFilenameOffset(output_name);
+  std::string output_basename = basename_offset != std::string::npos
+                                    ? output_name.substr(basename_offset)
+                                    : output_name;
+  size_t ext_offset = FindExtensionOffset(output_basename);
+  std::string product_name = ext_offset != std::string::npos
+                                 ? output_basename.substr(0, ext_offset - 1)
+                                 : output_basename;
+
+  PBXAttributes attributes = extra_attributes;
+  attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
+  attributes["CODE_SIGNING_REQUIRED"] = "NO";
+  attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
+  attributes["PRODUCT_NAME"] = product_name;
+  attributes["EXCLUDED_SOURCE_FILE_NAMES"] = "*.*";
+
+  targets_.push_back(std::make_unique<PBXNativeTarget>(
+      name, shell_script, config_name_, attributes, output_type, product_name,
+      product));
+  return static_cast<PBXNativeTarget*>(targets_.back().get());
+}
+
+void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
+  DCHECK(!project_dir_path.empty());
+  project_dir_path_.assign(project_dir_path);
+}
+
+void PBXProject::SetProjectRoot(const std::string& project_root) {
+  DCHECK(!project_root.empty());
+  project_root_.assign(project_root);
+}
+
+void PBXProject::AddTarget(std::unique_ptr<PBXTarget> target) {
+  DCHECK(target);
+  targets_.push_back(std::move(target));
+}
+
+PBXObjectClass PBXProject::Class() const {
+  return PBXProjectClass;
+}
+
+std::string PBXProject::Name() const {
+  return name_;
+}
+
+std::string PBXProject::Comment() const {
+  return "Project object";
+}
+
+void PBXProject::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  main_group_->Visit(visitor);
+  for (const auto& target : targets_) {
+    target->Visit(visitor);
+  }
+}
+
+void PBXProject::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  main_group_->Visit(visitor);
+  for (const auto& target : targets_) {
+    target->Visit(visitor);
+  }
+}
+void PBXProject::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "attributes", attributes_);
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "compatibilityVersion", "Xcode 3.2");
+  PrintProperty(out, rules, "developmentRegion", "en");
+  PrintProperty(out, rules, "hasScannedForEncodings", 1u);
+  PrintProperty(out, rules, "knownRegions",
+                std::vector<std::string>({"en", "Base"}));
+  PrintProperty(out, rules, "mainGroup", main_group_);
+  PrintProperty(out, rules, "projectDirPath", project_dir_path_);
+  PrintProperty(out, rules, "projectRoot", project_root_);
+  PrintProperty(out, rules, "targets", targets_);
+  out << indent_str << "};\n";
+}
+
+// PBXResourcesBuildPhase -----------------------------------------------------
+
+PBXResourcesBuildPhase::PBXResourcesBuildPhase() = default;
+
+PBXResourcesBuildPhase::~PBXResourcesBuildPhase() = default;
+
+PBXObjectClass PBXResourcesBuildPhase::Class() const {
+  return PBXResourcesBuildPhaseClass;
+}
+
+std::string PBXResourcesBuildPhase::Name() const {
+  return "Resources";
+}
+
+void PBXResourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", files_);
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  out << indent_str << "};\n";
+}
+
+// PBXShellScriptBuildPhase ---------------------------------------------------
+
+PBXShellScriptBuildPhase::PBXShellScriptBuildPhase(
+    const std::string& name,
+    const std::string& shell_script)
+    : name_("Action \"Compile and copy " + name + " via ninja\""),
+      shell_script_(shell_script) {}
+
+PBXShellScriptBuildPhase::~PBXShellScriptBuildPhase() = default;
+
+PBXObjectClass PBXShellScriptBuildPhase::Class() const {
+  return PBXShellScriptBuildPhaseClass;
+}
+
+std::string PBXShellScriptBuildPhase::Name() const {
+  return name_;
+}
+
+void PBXShellScriptBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", files_);
+  PrintProperty(out, rules, "inputPaths", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  PrintProperty(out, rules, "shellPath", "/usr/bin/python3");
+  PrintProperty(out, rules, "shellScript", shell_script_);
+  PrintProperty(out, rules, "showEnvVarsInLog", 0u);
+  out << indent_str << "};\n";
+}
+
+// PBXSourcesBuildPhase -------------------------------------------------------
+
+PBXSourcesBuildPhase::PBXSourcesBuildPhase() = default;
+
+PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default;
+
+PBXObjectClass PBXSourcesBuildPhase::Class() const {
+  return PBXSourcesBuildPhaseClass;
+}
+
+std::string PBXSourcesBuildPhase::Name() const {
+  return "Sources";
+}
+
+void PBXSourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", files_);
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  out << indent_str << "};\n";
+}
+
+PBXTargetDependency::PBXTargetDependency(
+    const PBXTarget* target,
+    std::unique_ptr<PBXContainerItemProxy> container_item_proxy)
+    : target_(target), container_item_proxy_(std::move(container_item_proxy)) {}
+
+PBXTargetDependency::~PBXTargetDependency() = default;
+
+PBXObjectClass PBXTargetDependency::Class() const {
+  return PBXTargetDependencyClass;
+}
+
+std::string PBXTargetDependency::Name() const {
+  return "PBXTargetDependency";
+}
+
+void PBXTargetDependency::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  container_item_proxy_->Visit(visitor);
+}
+
+void PBXTargetDependency::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  container_item_proxy_->Visit(visitor);
+}
+
+void PBXTargetDependency::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "target", target_);
+  PrintProperty(out, rules, "targetProxy", container_item_proxy_);
+  out << indent_str << "};\n";
+}
+
+// XCBuildConfiguration -------------------------------------------------------
+
+XCBuildConfiguration::XCBuildConfiguration(const std::string& name,
+                                           const PBXAttributes& attributes)
+    : attributes_(attributes), name_(name) {}
+
+XCBuildConfiguration::~XCBuildConfiguration() = default;
+
+PBXObjectClass XCBuildConfiguration::Class() const {
+  return XCBuildConfigurationClass;
+}
+
+std::string XCBuildConfiguration::Name() const {
+  return name_;
+}
+
+void XCBuildConfiguration::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildSettings", attributes_);
+  PrintProperty(out, rules, "name", name_);
+  out << indent_str << "};\n";
+}
+
+// XCConfigurationList --------------------------------------------------------
+
+XCConfigurationList::XCConfigurationList(const std::string& name,
+                                         const PBXAttributes& attributes,
+                                         const PBXObject* owner_reference)
+    : owner_reference_(owner_reference) {
+  DCHECK(owner_reference_);
+  configurations_.push_back(
+      std::make_unique<XCBuildConfiguration>(name, attributes));
+}
+
+XCConfigurationList::~XCConfigurationList() = default;
+
+PBXObjectClass XCConfigurationList::Class() const {
+  return XCConfigurationListClass;
+}
+
+std::string XCConfigurationList::Name() const {
+  std::stringstream buffer;
+  buffer << "Build configuration list for "
+         << ToString(owner_reference_->Class()) << " \""
+         << owner_reference_->Name() << "\"";
+  return buffer.str();
+}
+
+void XCConfigurationList::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  for (const auto& configuration : configurations_) {
+    configuration->Visit(visitor);
+  }
+}
+
+void XCConfigurationList::Visit(PBXObjectVisitorConst& visitor) const {
+  PBXObject::Visit(visitor);
+  for (const auto& configuration : configurations_) {
+    configuration->Visit(visitor);
+  }
+}
+
+void XCConfigurationList::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurations", configurations_);
+  PrintProperty(out, rules, "defaultConfigurationIsVisible", 1u);
+  PrintProperty(out, rules, "defaultConfigurationName",
+                configurations_[0]->Name());
+  out << indent_str << "};\n";
+}
diff --git a/src/gn/xcode_object.h b/src/gn/xcode_object.h
new file mode 100644 (file)
index 0000000..82dec5e
--- /dev/null
@@ -0,0 +1,512 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_XCODE_OBJECT_H_
+#define TOOLS_GN_XCODE_OBJECT_H_
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+// Helper classes to generate Xcode project files.
+//
+// This code is based on gyp xcodeproj_file.py generator. It does not support
+// all features of Xcode project but instead just enough to implement a hybrid
+// mode where Xcode uses external scripts to perform the compilation steps.
+//
+// See
+// https://chromium.googlesource.com/external/gyp/+/master/pylib/gyp/xcodeproj_file.py
+// for more information on Xcode project file format.
+
+// PBXObjectClass -------------------------------------------------------------
+
+enum PBXObjectClass {
+  // Those values needs to stay sorted in alphabetic order.
+  PBXAggregateTargetClass,
+  PBXBuildFileClass,
+  PBXContainerItemProxyClass,
+  PBXFileReferenceClass,
+  PBXFrameworksBuildPhaseClass,
+  PBXGroupClass,
+  PBXNativeTargetClass,
+  PBXProjectClass,
+  PBXResourcesBuildPhaseClass,
+  PBXShellScriptBuildPhaseClass,
+  PBXSourcesBuildPhaseClass,
+  PBXTargetDependencyClass,
+  XCBuildConfigurationClass,
+  XCConfigurationListClass,
+};
+
+const char* ToString(PBXObjectClass cls);
+
+// Forward-declarations -------------------------------------------------------
+
+class PBXAggregateTarget;
+class PBXBuildFile;
+class PBXBuildPhase;
+class PBXContainerItemProxy;
+class PBXFileReference;
+class PBXFrameworksBuildPhase;
+class PBXGroup;
+class PBXNativeTarget;
+class PBXObject;
+class PBXProject;
+class PBXResourcesBuildPhase;
+class PBXShellScriptBuildPhase;
+class PBXSourcesBuildPhase;
+class PBXTarget;
+class PBXTargetDependency;
+class XCBuildConfiguration;
+class XCConfigurationList;
+
+using PBXAttributes = std::map<std::string, std::string>;
+
+// PBXObjectVisitor -----------------------------------------------------------
+
+class PBXObjectVisitor {
+ public:
+  PBXObjectVisitor();
+  virtual ~PBXObjectVisitor();
+  virtual void Visit(PBXObject* object) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXObjectVisitor);
+};
+
+// PBXObjectVisitorConst ------------------------------------------------------
+
+class PBXObjectVisitorConst {
+ public:
+  PBXObjectVisitorConst();
+  virtual ~PBXObjectVisitorConst();
+  virtual void Visit(const PBXObject* object) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXObjectVisitorConst);
+};
+
+// PBXObject ------------------------------------------------------------------
+
+class PBXObject {
+ public:
+  PBXObject();
+  virtual ~PBXObject();
+
+  const std::string id() const { return id_; }
+  void SetId(const std::string& id);
+
+  std::string Reference() const;
+
+  virtual PBXObjectClass Class() const = 0;
+  virtual std::string Name() const = 0;
+  virtual std::string Comment() const;
+  virtual void Visit(PBXObjectVisitor& visitor);
+  virtual void Visit(PBXObjectVisitorConst& visitor) const;
+  virtual void Print(std::ostream& out, unsigned indent) const = 0;
+
+ private:
+  std::string id_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXObject);
+};
+
+// PBXBuildPhase --------------------------------------------------------------
+
+class PBXBuildPhase : public PBXObject {
+ public:
+  PBXBuildPhase();
+  ~PBXBuildPhase() override;
+
+  void AddBuildFile(std::unique_ptr<PBXBuildFile> build_file);
+
+  // PBXObject implementation.
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+
+ protected:
+  std::vector<std::unique_ptr<PBXBuildFile>> files_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXBuildPhase);
+};
+
+// PBXTarget ------------------------------------------------------------------
+
+class PBXTarget : public PBXObject {
+ public:
+  PBXTarget(const std::string& name,
+            const std::string& shell_script,
+            const std::string& config_name,
+            const PBXAttributes& attributes);
+  ~PBXTarget() override;
+
+  void AddDependency(std::unique_ptr<PBXTargetDependency> dependency);
+
+  // PBXObject implementation.
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+
+ protected:
+  std::unique_ptr<XCConfigurationList> configurations_;
+  std::vector<std::unique_ptr<PBXBuildPhase>> build_phases_;
+  std::vector<std::unique_ptr<PBXTargetDependency>> dependencies_;
+  PBXSourcesBuildPhase* source_build_phase_ = nullptr;
+  PBXResourcesBuildPhase* resource_build_phase_ = nullptr;
+  std::string name_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXTarget);
+};
+
+// PBXAggregateTarget ---------------------------------------------------------
+
+class PBXAggregateTarget : public PBXTarget {
+ public:
+  PBXAggregateTarget(const std::string& name,
+                     const std::string& shell_script,
+                     const std::string& config_name,
+                     const PBXAttributes& attributes);
+  ~PBXAggregateTarget() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXAggregateTarget);
+};
+
+// PBXBuildFile ---------------------------------------------------------------
+
+class PBXBuildFile : public PBXObject {
+ public:
+  PBXBuildFile(const PBXFileReference* file_reference,
+               const PBXBuildPhase* build_phase);
+  ~PBXBuildFile() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXFileReference* file_reference_ = nullptr;
+  const PBXBuildPhase* build_phase_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXBuildFile);
+};
+
+// PBXContainerItemProxy ------------------------------------------------------
+class PBXContainerItemProxy : public PBXObject {
+ public:
+  PBXContainerItemProxy(const PBXProject* project, const PBXTarget* target);
+  ~PBXContainerItemProxy() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXProject* project_ = nullptr;
+  const PBXTarget* target_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXContainerItemProxy);
+};
+
+// PBXFileReference -----------------------------------------------------------
+
+class PBXFileReference : public PBXObject {
+ public:
+  PBXFileReference(const std::string& name,
+                   const std::string& path,
+                   const std::string& type);
+  ~PBXFileReference() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  std::string Comment() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+  const std::string& path() const { return path_; }
+
+ private:
+  std::string name_;
+  std::string path_;
+  std::string type_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXFileReference);
+};
+
+// PBXFrameworksBuildPhase ----------------------------------------------------
+
+class PBXFrameworksBuildPhase : public PBXBuildPhase {
+ public:
+  PBXFrameworksBuildPhase();
+  ~PBXFrameworksBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXFrameworksBuildPhase);
+};
+
+// PBXGroup -------------------------------------------------------------------
+
+class PBXGroup : public PBXObject {
+ public:
+  explicit PBXGroup(const std::string& path = std::string(),
+                    const std::string& name = std::string());
+  ~PBXGroup() override;
+
+  const std::string& path() const { return path_; }
+  const std::string& name() const { return name_; }
+
+  PBXFileReference* AddSourceFile(const std::string& navigator_path,
+                                  const std::string& source_path);
+
+  bool is_source() const { return is_source_; }
+  void set_is_source(bool is_source) { is_source_ = is_source; }
+
+  bool autosorted() const { return autosorted_; }
+  void set_autosorted(bool autosorted) { autosorted_ = autosorted; }
+
+  template <typename T, typename... Args>
+  T* CreateChild(Args&&... args) {
+    return static_cast<T*>(
+        AddChildImpl(std::make_unique<T>(std::forward<Args>(args)...)));
+  }
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  PBXObject* AddChildImpl(std::unique_ptr<PBXObject> child);
+
+  std::vector<std::unique_ptr<PBXObject>> children_;
+  std::string name_;
+  std::string path_;
+  bool is_source_ = false;
+  bool autosorted_ = true;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXGroup);
+};
+
+// PBXNativeTarget ------------------------------------------------------------
+
+class PBXNativeTarget : public PBXTarget {
+ public:
+  PBXNativeTarget(const std::string& name,
+                  const std::string& shell_script,
+                  const std::string& config_name,
+                  const PBXAttributes& attributes,
+                  const std::string& product_type,
+                  const std::string& product_name,
+                  const PBXFileReference* product_reference);
+  ~PBXNativeTarget() override;
+
+  void AddResourceFile(const PBXFileReference* file_reference);
+
+  void AddFileForIndexing(const PBXFileReference* file_reference);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXFileReference* product_reference_ = nullptr;
+  std::string product_type_;
+  std::string product_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXNativeTarget);
+};
+
+// PBXProject -----------------------------------------------------------------
+
+class PBXProject : public PBXObject {
+ public:
+  PBXProject(const std::string& name,
+             const std::string& config_name,
+             const std::string& source_path,
+             const PBXAttributes& attributes);
+  ~PBXProject() override;
+
+  void AddSourceFileToIndexingTarget(const std::string& navigator_path,
+                                     const std::string& source_path);
+  void AddSourceFile(const std::string& navigator_path,
+                     const std::string& source_path,
+                     PBXNativeTarget* target);
+  void AddAggregateTarget(const std::string& name,
+                          const std::string& shell_script);
+  void AddIndexingTarget();
+  PBXNativeTarget* AddNativeTarget(
+      const std::string& name,
+      const std::string& type,
+      const std::string& output_dir,
+      const std::string& output_name,
+      const std::string& output_type,
+      const std::string& shell_script,
+      const PBXAttributes& extra_attributes = PBXAttributes());
+
+  void SetProjectDirPath(const std::string& project_dir_path);
+  void SetProjectRoot(const std::string& project_root);
+  void AddTarget(std::unique_ptr<PBXTarget> target);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  std::string Comment() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  PBXAttributes attributes_;
+  std::unique_ptr<XCConfigurationList> configurations_;
+  std::unique_ptr<PBXGroup> main_group_;
+  std::string project_dir_path_;
+  std::string project_root_;
+  std::vector<std::unique_ptr<PBXTarget>> targets_;
+  std::string name_;
+  std::string config_name_;
+
+  PBXGroup* sources_ = nullptr;
+  PBXGroup* products_ = nullptr;
+  PBXNativeTarget* target_for_indexing_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXProject);
+};
+
+// PBXResourcesBuildPhase -----------------------------------------------------
+
+class PBXResourcesBuildPhase : public PBXBuildPhase {
+ public:
+  PBXResourcesBuildPhase();
+  ~PBXResourcesBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXResourcesBuildPhase);
+};
+
+// PBXShellScriptBuildPhase ---------------------------------------------------
+
+class PBXShellScriptBuildPhase : public PBXBuildPhase {
+ public:
+  PBXShellScriptBuildPhase(const std::string& name,
+                           const std::string& shell_script);
+  ~PBXShellScriptBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::string name_;
+  std::string shell_script_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXShellScriptBuildPhase);
+};
+
+// PBXSourcesBuildPhase -------------------------------------------------------
+
+class PBXSourcesBuildPhase : public PBXBuildPhase {
+ public:
+  PBXSourcesBuildPhase();
+  ~PBXSourcesBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXSourcesBuildPhase);
+};
+
+// PBXTargetDependency -----------------------------------------------------
+class PBXTargetDependency : public PBXObject {
+ public:
+  PBXTargetDependency(
+      const PBXTarget* target,
+      std::unique_ptr<PBXContainerItemProxy> container_item_proxy);
+  ~PBXTargetDependency() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXTarget* target_ = nullptr;
+  std::unique_ptr<PBXContainerItemProxy> container_item_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXTargetDependency);
+};
+
+// XCBuildConfiguration -------------------------------------------------------
+
+class XCBuildConfiguration : public PBXObject {
+ public:
+  XCBuildConfiguration(const std::string& name,
+                       const PBXAttributes& attributes);
+  ~XCBuildConfiguration() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  PBXAttributes attributes_;
+  std::string name_;
+
+  DISALLOW_COPY_AND_ASSIGN(XCBuildConfiguration);
+};
+
+// XCConfigurationList --------------------------------------------------------
+
+class XCConfigurationList : public PBXObject {
+ public:
+  XCConfigurationList(const std::string& name,
+                      const PBXAttributes& attributes,
+                      const PBXObject* owner_reference);
+  ~XCConfigurationList() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Visit(PBXObjectVisitorConst& visitor) const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::vector<std::unique_ptr<XCBuildConfiguration>> configurations_;
+  const PBXObject* owner_reference_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(XCConfigurationList);
+};
+
+#endif  // TOOLS_GN_XCODE_OBJECT_H_
diff --git a/src/gn/xcode_object_unittest.cc b/src/gn/xcode_object_unittest.cc
new file mode 100644 (file)
index 0000000..0498c3f
--- /dev/null
@@ -0,0 +1,435 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/xcode_object.h"
+
+#include "util/test/test.h"
+
+namespace {
+
+// Instantiate a PBXSourcesBuildPhase object.
+std::unique_ptr<PBXSourcesBuildPhase> GetPBXSourcesBuildPhaseObject() {
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase(
+      new PBXSourcesBuildPhase());
+  return pbx_sources_build_phase;
+}
+
+// Instantiate a PBXFrameworksBuildPhase object.
+std::unique_ptr<PBXFrameworksBuildPhase> GetPBXFrameworksBuildPhaseObject() {
+  std::unique_ptr<PBXFrameworksBuildPhase> pbx_frameworks_build_phase(
+      new PBXFrameworksBuildPhase());
+  return pbx_frameworks_build_phase;
+}
+
+// Instantiate a PBXShellScriptBuildPhase object with arbitrary names.
+std::unique_ptr<PBXShellScriptBuildPhase> GetPBXShellScriptBuildPhaseObject() {
+  std::unique_ptr<PBXShellScriptBuildPhase> pbx_shell_script_build_phase(
+      new PBXShellScriptBuildPhase("name", "shell_script"));
+  return pbx_shell_script_build_phase;
+}
+
+// Instantiate a PBXGroup object with arbitrary names.
+std::unique_ptr<PBXGroup> GetPBXGroupObject() {
+  std::unique_ptr<PBXGroup> pbx_group(new PBXGroup("/dir1/dir2", "group"));
+  return pbx_group;
+}
+
+// Instantiate a PBXProject object with arbitrary names.
+std::unique_ptr<PBXProject> GetPBXProjectObject() {
+  std::unique_ptr<PBXProject> pbx_project(
+      new PBXProject("project", "config", "out/build", PBXAttributes()));
+  return pbx_project;
+}
+
+// Instantiate a PBXFileReference object with arbitrary names.
+std::unique_ptr<PBXFileReference> GetPBXFileReferenceObject() {
+  std::unique_ptr<PBXFileReference> pbx_file_reference(new PBXFileReference(
+      "product.app", "product.app", "wrapper.application"));
+  return pbx_file_reference;
+}
+
+// Instantiate a PBXBuildFile object.
+std::unique_ptr<PBXBuildFile> GetPBXBuildFileObject(
+    const PBXFileReference* file_reference,
+    const PBXSourcesBuildPhase* build_phase) {
+  std::unique_ptr<PBXBuildFile> pbx_build_file(
+      new PBXBuildFile(file_reference, build_phase));
+  return pbx_build_file;
+}
+
+// Instantiate a PBXAggregateTarget object with arbitrary names.
+std::unique_ptr<PBXAggregateTarget> GetPBXAggregateTargetObject() {
+  std::unique_ptr<PBXAggregateTarget> pbx_aggregate_target(
+      new PBXAggregateTarget("target_name", "shell_script", "config_name",
+                             PBXAttributes()));
+  return pbx_aggregate_target;
+}
+
+// Instantiate a PBXNativeTarget object with arbitrary names.
+std::unique_ptr<PBXNativeTarget> GetPBXNativeTargetObject(
+    const PBXFileReference* product_reference) {
+  std::unique_ptr<PBXNativeTarget> pbx_native_target(new PBXNativeTarget(
+      "target_name", "ninja gn_unittests", "config_name", PBXAttributes(),
+      "com.apple.product-type.application", "product_name", product_reference));
+  return pbx_native_target;
+}
+
+// Instantiate a PBXContainerItemProxy object.
+std::unique_ptr<PBXContainerItemProxy> GetPBXContainerItemProxyObject(
+    const PBXProject* project,
+    const PBXTarget* target) {
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy(
+      new PBXContainerItemProxy(project, target));
+  return pbx_container_item_proxy;
+}
+
+// Instantiate a PBXTargetDependency object.
+std::unique_ptr<PBXTargetDependency> GetPBXTargetDependencyObject(
+    const PBXTarget* target,
+    std::unique_ptr<PBXContainerItemProxy> container_item_proxy) {
+  std::unique_ptr<PBXTargetDependency> pbx_target_dependency(
+      new PBXTargetDependency(target, std::move(container_item_proxy)));
+  return pbx_target_dependency;
+}
+
+// Instantiate a XCBuildConfiguration object with arbitrary names.
+std::unique_ptr<XCBuildConfiguration> GetXCBuildConfigurationObject() {
+  std::unique_ptr<XCBuildConfiguration> xc_build_configuration(
+      new XCBuildConfiguration("config_name", PBXAttributes()));
+  return xc_build_configuration;
+}
+
+// Instantiate a XCConfigurationList object with arbitrary names.
+std::unique_ptr<XCConfigurationList> GetXCConfigurationListObject(
+    const PBXObject* owner_reference) {
+  std::unique_ptr<XCConfigurationList> xc_configuration_list(
+      new XCConfigurationList("config_list_name", PBXAttributes(),
+                              owner_reference));
+  return xc_configuration_list;
+}
+
+}  // namespace
+
+// Tests that instantiating Xcode objects doesn't crash.
+TEST(XcodeObject, InstantiatePBXSourcesBuildPhase) {
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+}
+
+TEST(XcodeObject, InstantiatePBXFrameworksBuildPhase) {
+  std::unique_ptr<PBXFrameworksBuildPhase> pbx_frameworks_build_phase =
+      GetPBXFrameworksBuildPhaseObject();
+}
+
+TEST(XcodeObject, InstantiatePBXShellScriptBuildPhase) {
+  std::unique_ptr<PBXShellScriptBuildPhase> pbx_shell_script_build_phase =
+      GetPBXShellScriptBuildPhaseObject();
+}
+
+TEST(XcodeObject, InstantiatePBXGroup) {
+  std::unique_ptr<PBXGroup> pbx_group = GetPBXGroupObject();
+}
+
+TEST(XcodeObject, InstantiatePBXProject) {
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+}
+
+TEST(XcodeObject, InstantiatePBXFileReference) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+}
+
+TEST(XcodeObject, InstantiatePBXBuildFile) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+  std::unique_ptr<PBXBuildFile> pbx_build_file = GetPBXBuildFileObject(
+      pbx_file_reference.get(), pbx_sources_build_phase.get());
+}
+
+TEST(XcodeObject, InstantiatePBXAggregateTarget) {
+  std::unique_ptr<PBXAggregateTarget> pbx_aggregate_target =
+      GetPBXAggregateTargetObject();
+}
+
+TEST(XcodeObject, InstantiatePBXNativeTarget) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+}
+
+TEST(XcodeObject, InstantiatePBXContainerItemProxy) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+}
+
+TEST(XcodeObject, InstantiatePBXTargetDependency) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+  std::unique_ptr<PBXTargetDependency> pbx_target_dependency =
+      GetPBXTargetDependencyObject(pbx_native_target.get(),
+                                   std::move(pbx_container_item_proxy));
+}
+
+TEST(XcodeObject, InstantiateXCBuildConfiguration) {
+  std::unique_ptr<XCBuildConfiguration> xc_build_configuration =
+      GetXCBuildConfigurationObject();
+}
+
+TEST(XcodeObject, InstantiateXCConfigurationList) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<XCConfigurationList> xc_configuration_list =
+      GetXCConfigurationListObject(pbx_native_target.get());
+}
+
+// Tests that the mapping between PBXObject and PBXObjectClass.
+TEST(XcodeObject, PBXSourcesBuildPhaseObjectToClass) {
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+  EXPECT_EQ(PBXSourcesBuildPhaseClass, pbx_sources_build_phase->Class());
+}
+
+TEST(XcodeObject, PBXFrameworksBuildPhaseObjectToClass) {
+  std::unique_ptr<PBXFrameworksBuildPhase> pbx_frameworks_build_phase =
+      GetPBXFrameworksBuildPhaseObject();
+  EXPECT_EQ(PBXFrameworksBuildPhaseClass, pbx_frameworks_build_phase->Class());
+}
+
+TEST(XcodeObject, PBXShellScriptBuildPhaseObjectToClass) {
+  std::unique_ptr<PBXShellScriptBuildPhase> pbx_shell_script_build_phase =
+      GetPBXShellScriptBuildPhaseObject();
+  EXPECT_EQ(PBXShellScriptBuildPhaseClass,
+            pbx_shell_script_build_phase->Class());
+}
+
+TEST(XcodeObject, PBXGroupObjectToClass) {
+  std::unique_ptr<PBXGroup> pbx_group = GetPBXGroupObject();
+  EXPECT_EQ(PBXGroupClass, pbx_group->Class());
+}
+
+TEST(XcodeObject, PBXProjectObjectToClass) {
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  EXPECT_EQ(PBXProjectClass, pbx_project->Class());
+}
+
+TEST(XcodeObject, PBXFileReferenceObjectToClass) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+  EXPECT_EQ(PBXFileReferenceClass, pbx_file_reference->Class());
+}
+
+TEST(XcodeObject, PBXBuildFileObjectToClass) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+  std::unique_ptr<PBXBuildFile> pbx_build_file = GetPBXBuildFileObject(
+      pbx_file_reference.get(), pbx_sources_build_phase.get());
+  EXPECT_EQ(PBXBuildFileClass, pbx_build_file->Class());
+}
+
+TEST(XcodeObject, PBXAggregateTargetObjectToClass) {
+  std::unique_ptr<PBXAggregateTarget> pbx_aggregate_target =
+      GetPBXAggregateTargetObject();
+  EXPECT_EQ(PBXAggregateTargetClass, pbx_aggregate_target->Class());
+}
+
+TEST(XcodeObject, PBXNativeTargetObjectToClass) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  EXPECT_EQ(PBXNativeTargetClass, pbx_native_target->Class());
+}
+
+TEST(XcodeObject, PBXContainerItemProxyObjectToClass) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+  EXPECT_EQ(PBXContainerItemProxyClass, pbx_container_item_proxy->Class());
+}
+
+TEST(XcodeObject, PBXTargetDependencyObjectToClass) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+  std::unique_ptr<PBXTargetDependency> pbx_target_dependency =
+      GetPBXTargetDependencyObject(pbx_native_target.get(),
+                                   std::move(pbx_container_item_proxy));
+  EXPECT_EQ(PBXTargetDependencyClass, pbx_target_dependency->Class());
+}
+
+TEST(XcodeObject, XCBuildConfigurationObjectToClass) {
+  std::unique_ptr<XCBuildConfiguration> xc_build_configuration =
+      GetXCBuildConfigurationObject();
+  EXPECT_EQ(XCBuildConfigurationClass, xc_build_configuration->Class());
+}
+
+TEST(XcodeObject, XCConfigurationListObjectToClass) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<XCConfigurationList> xc_configuration_list =
+      GetXCConfigurationListObject(pbx_native_target.get());
+  EXPECT_EQ(XCConfigurationListClass, xc_configuration_list->Class());
+}
+
+// Tests the mapping between PBXObjectClass and it's name as a string.
+TEST(XcodeObject, ClassToString) {
+  EXPECT_STREQ("PBXAggregateTarget", ToString(PBXAggregateTargetClass));
+  EXPECT_STREQ("PBXBuildFile", ToString(PBXBuildFileClass));
+  EXPECT_STREQ("PBXAggregateTarget", ToString(PBXAggregateTargetClass));
+  EXPECT_STREQ("PBXBuildFile", ToString(PBXBuildFileClass));
+  EXPECT_STREQ("PBXContainerItemProxy", ToString(PBXContainerItemProxyClass));
+  EXPECT_STREQ("PBXFileReference", ToString(PBXFileReferenceClass));
+  EXPECT_STREQ("PBXFrameworksBuildPhase",
+               ToString(PBXFrameworksBuildPhaseClass));
+  EXPECT_STREQ("PBXGroup", ToString(PBXGroupClass));
+  EXPECT_STREQ("PBXNativeTarget", ToString(PBXNativeTargetClass));
+  EXPECT_STREQ("PBXProject", ToString(PBXProjectClass));
+  EXPECT_STREQ("PBXSourcesBuildPhase", ToString(PBXSourcesBuildPhaseClass));
+  EXPECT_STREQ("PBXTargetDependency", ToString(PBXTargetDependencyClass));
+  EXPECT_STREQ("XCBuildConfiguration", ToString(XCBuildConfigurationClass));
+  EXPECT_STREQ("XCConfigurationList", ToString(XCConfigurationListClass));
+  EXPECT_STREQ("PBXShellScriptBuildPhase",
+               ToString(PBXShellScriptBuildPhaseClass));
+}
+
+// Tests the mapping between PBXObject and it's name as a string.
+TEST(XcodeObject, PBXSourcesBuildPhaseName) {
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+  EXPECT_EQ("Sources", pbx_sources_build_phase->Name());
+}
+
+TEST(XcodeObject, PBXFrameworksBuildPhaseName) {
+  std::unique_ptr<PBXFrameworksBuildPhase> pbx_frameworks_build_phase =
+      GetPBXFrameworksBuildPhaseObject();
+  EXPECT_EQ("Frameworks", pbx_frameworks_build_phase->Name());
+}
+
+TEST(XcodeObject, PBXShellScriptBuildPhaseName) {
+  std::unique_ptr<PBXShellScriptBuildPhase> pbx_shell_script_build_phase =
+      GetPBXShellScriptBuildPhaseObject();
+  EXPECT_EQ("Action \"Compile and copy name via ninja\"",
+            pbx_shell_script_build_phase->Name());
+}
+
+TEST(XcodeObject, PBXGroupName) {
+  PBXGroup pbx_group_with_name(std::string(), "name");
+  EXPECT_EQ("name", pbx_group_with_name.Name());
+
+  PBXGroup pbx_group_with_path("path", std::string());
+  EXPECT_EQ("path", pbx_group_with_path.Name());
+
+  PBXGroup pbx_group_empty{std::string(), std::string()};
+  EXPECT_EQ(std::string(), pbx_group_empty.Name());
+}
+
+TEST(XcodeObject, PBXProjectName) {
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  EXPECT_EQ("project", pbx_project->Name());
+}
+
+TEST(XcodeObject, PBXFileReferenceName) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+  EXPECT_EQ("product.app", pbx_file_reference->Name());
+}
+
+TEST(XcodeObject, PBXBuildFileName) {
+  std::unique_ptr<PBXFileReference> pbx_file_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXSourcesBuildPhase> pbx_sources_build_phase =
+      GetPBXSourcesBuildPhaseObject();
+  std::unique_ptr<PBXBuildFile> pbx_build_file = GetPBXBuildFileObject(
+      pbx_file_reference.get(), pbx_sources_build_phase.get());
+  EXPECT_EQ("product.app in Sources", pbx_build_file->Name());
+}
+
+TEST(XcodeObject, PBXAggregateTargetName) {
+  std::unique_ptr<PBXAggregateTarget> pbx_aggregate_target =
+      GetPBXAggregateTargetObject();
+  EXPECT_EQ("target_name", pbx_aggregate_target->Name());
+}
+
+TEST(XcodeObject, PBXNativeTargetName) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  EXPECT_EQ("target_name", pbx_native_target->Name());
+}
+
+TEST(XcodeObject, PBXContainerItemProxyName) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+  EXPECT_EQ("PBXContainerItemProxy", pbx_container_item_proxy->Name());
+}
+
+TEST(XcodeObject, PBXTargetDependencyName) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXProject> pbx_project = GetPBXProjectObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<PBXContainerItemProxy> pbx_container_item_proxy =
+      GetPBXContainerItemProxyObject(pbx_project.get(),
+                                     pbx_native_target.get());
+  std::unique_ptr<PBXTargetDependency> pbx_target_dependency =
+      GetPBXTargetDependencyObject(pbx_native_target.get(),
+                                   std::move(pbx_container_item_proxy));
+  EXPECT_EQ("PBXTargetDependency", pbx_target_dependency->Name());
+}
+
+TEST(XcodeObject, XCBuildConfigurationName) {
+  std::unique_ptr<XCBuildConfiguration> xc_build_configuration =
+      GetXCBuildConfigurationObject();
+  EXPECT_EQ("config_name", xc_build_configuration->Name());
+}
+
+TEST(XcodeObject, XCConfigurationListName) {
+  std::unique_ptr<PBXFileReference> product_reference =
+      GetPBXFileReferenceObject();
+  std::unique_ptr<PBXNativeTarget> pbx_native_target =
+      GetPBXNativeTargetObject(product_reference.get());
+  std::unique_ptr<XCConfigurationList> xc_configuration_list =
+      GetXCConfigurationListObject(pbx_native_target.get());
+  EXPECT_EQ("Build configuration list for PBXNativeTarget \"target_name\"",
+            xc_configuration_list->Name());
+}
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
new file mode 100644 (file)
index 0000000..a5650fa
--- /dev/null
@@ -0,0 +1,1029 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/xcode_writer.h"
+
+#include <iomanip>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "gn/args.h"
+#include "gn/build_settings.h"
+#include "gn/builder.h"
+#include "gn/commands.h"
+#include "gn/deps_iterator.h"
+#include "gn/filesystem_utils.h"
+#include "gn/item.h"
+#include "gn/loader.h"
+#include "gn/scheduler.h"
+#include "gn/settings.h"
+#include "gn/source_file.h"
+#include "gn/substitution_writer.h"
+#include "gn/target.h"
+#include "gn/value.h"
+#include "gn/variables.h"
+#include "gn/xcode_object.h"
+
+namespace {
+
+// This is the template of the script used to build the target. It invokes
+// ninja (supporting --ninja-executable parameter), parsing ninja's output
+// using a regular expression looking for relative path to the source root
+// from root_build_dir that are at the start of a path and converting them
+// to absolute paths (use str.replace(rel_root_src, abs_root_src) would be
+// simpler but would fail if rel_root_src is present multiple time in the
+// path).
+const char kBuildScriptTemplate[] = R"(
+import re
+import os
+import subprocess
+import sys
+
+rel_root_src = '%s'
+abs_root_src = os.path.abspath(rel_root_src) + '/'
+
+build_target = '%s'
+ninja_binary = '%s'
+ninja_params = [ '-C', '.' ]
+
+%s
+
+if build_target:
+  ninja_params.append(build_target)
+  print('Compile "' + build_target + '" via ninja')
+else:
+  print('Compile "all" via ninja')
+
+process = subprocess.Popen(
+    [ ninja_binary ] + ninja_params,
+    stdout=subprocess.PIPE,
+    stderr=subprocess.STDOUT,
+    universal_newlines=True,
+    encoding='utf-8',
+    env=environ)
+
+pattern = re.compile('(?<!/)' + re.escape(rel_root_src))
+
+for line in iter(process.stdout.readline, ''):
+  while True:
+    match = pattern.search(line)
+    if not match:
+      break
+    span = match.span()
+    print(line[:span[0]], end='')
+    print(abs_root_src, end='')
+    line = line[span[1]:]
+  print(line, flush=True, end='')
+
+process.wait()
+
+sys.exit(process.returncode)
+)";
+
+enum TargetOsType {
+  WRITER_TARGET_OS_IOS,
+  WRITER_TARGET_OS_MACOS,
+};
+
+const char* kXCTestFileSuffixes[] = {
+    "egtest.m", "egtest.mm", "xctest.m", "xctest.mm", "UITests.m", "UITests.mm",
+};
+
+const char kXCTestModuleTargetNamePostfix[] = "_module";
+const char kXCUITestRunnerTargetNamePostfix[] = "_runner";
+
+struct SafeEnvironmentVariableInfo {
+  const char* name;
+  bool capture_at_generation;
+};
+
+SafeEnvironmentVariableInfo kSafeEnvironmentVariables[] = {
+    {"HOME", true},
+    {"LANG", true},
+    {"PATH", true},
+    {"USER", true},
+    {"TMPDIR", false},
+    {"ICECC_VERSION", true},
+    {"ICECC_CLANG_REMOTE_CPP", true}};
+
+TargetOsType GetTargetOs(const Args& args) {
+  const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
+  if (target_os_value) {
+    if (target_os_value->type() == Value::STRING) {
+      if (target_os_value->string_value() == "ios")
+        return WRITER_TARGET_OS_IOS;
+    }
+  }
+  return WRITER_TARGET_OS_MACOS;
+}
+
+std::string GetNinjaExecutable(const std::string& ninja_executable) {
+  return ninja_executable.empty() ? "ninja" : ninja_executable;
+}
+
+std::string ComputeScriptEnviron(base::Environment* environment) {
+  std::stringstream buffer;
+  buffer << "environ = {}";
+  for (const auto& variable : kSafeEnvironmentVariables) {
+    buffer << "\nenviron['" << variable.name << "'] = ";
+    if (variable.capture_at_generation) {
+      std::string value;
+      environment->GetVar(variable.name, &value);
+      buffer << "'" << value << "'";
+    } else {
+      buffer << "os.environ.get('" << variable.name << "', '')";
+    }
+  }
+  return buffer.str();
+}
+
+std::string GetBuildScript(const std::string& target_name,
+                           const std::string& ninja_executable,
+                           const std::string& root_src_dir,
+                           base::Environment* environment) {
+  std::string environ_script = ComputeScriptEnviron(environment);
+  std::string ninja = GetNinjaExecutable(ninja_executable);
+  return base::StringPrintf(kBuildScriptTemplate, root_src_dir.c_str(),
+                            target_name.c_str(), ninja.c_str(),
+                            environ_script.c_str());
+}
+
+bool IsApplicationTarget(const Target* target) {
+  return target->output_type() == Target::CREATE_BUNDLE &&
+         target->bundle_data().product_type() ==
+             "com.apple.product-type.application";
+}
+
+bool IsXCUITestRunnerTarget(const Target* target) {
+  return IsApplicationTarget(target) &&
+         base::EndsWith(target->label().name(),
+                        kXCUITestRunnerTargetNamePostfix,
+                        base::CompareCase::SENSITIVE);
+}
+
+bool IsXCTestModuleTarget(const Target* target) {
+  return target->output_type() == Target::CREATE_BUNDLE &&
+         target->bundle_data().product_type() ==
+             "com.apple.product-type.bundle.unit-test" &&
+         base::EndsWith(target->label().name(), kXCTestModuleTargetNamePostfix,
+                        base::CompareCase::SENSITIVE);
+}
+
+bool IsXCUITestModuleTarget(const Target* target) {
+  return target->output_type() == Target::CREATE_BUNDLE &&
+         target->bundle_data().product_type() ==
+             "com.apple.product-type.bundle.ui-testing" &&
+         base::EndsWith(target->label().name(), kXCTestModuleTargetNamePostfix,
+                        base::CompareCase::SENSITIVE);
+}
+
+bool IsXCTestFile(const SourceFile& file) {
+  std::string file_name = file.GetName();
+  for (size_t i = 0; i < std::size(kXCTestFileSuffixes); ++i) {
+    if (base::EndsWith(file_name, kXCTestFileSuffixes[i],
+                       base::CompareCase::SENSITIVE)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+// Finds the application target from its target name.
+std::optional<std::pair<const Target*, PBXNativeTarget*>>
+FindApplicationTargetByName(
+    const ParseNode* node,
+    const std::string& target_name,
+    const std::map<const Target*, PBXNativeTarget*>& targets,
+    Err* err) {
+  for (auto& pair : targets) {
+    const Target* target = pair.first;
+    if (target->label().name() == target_name) {
+      if (!IsApplicationTarget(target)) {
+        *err = Err(node, "host application target \"" + target_name +
+                             "\" not an application bundle");
+        return std::nullopt;
+      }
+      DCHECK(pair.first);
+      DCHECK(pair.second);
+      return pair;
+    }
+  }
+  *err =
+      Err(node, "cannot find host application bundle \"" + target_name + "\"");
+  return std::nullopt;
+}
+
+// Adds |base_pbxtarget| as a dependency of |dependent_pbxtarget| in the
+// generated Xcode project.
+void AddPBXTargetDependency(const PBXTarget* base_pbxtarget,
+                            PBXTarget* dependent_pbxtarget,
+                            const PBXProject* project) {
+  auto container_item_proxy =
+      std::make_unique<PBXContainerItemProxy>(project, base_pbxtarget);
+  auto dependency = std::make_unique<PBXTargetDependency>(
+      base_pbxtarget, std::move(container_item_proxy));
+
+  dependent_pbxtarget->AddDependency(std::move(dependency));
+}
+
+// Helper class to resolve list of XCTest files per target.
+//
+// Uses a cache of file found per intermediate targets to reduce the need
+// to shared targets multiple times. It is recommended to reuse the same
+// object to resolve all the targets for a project.
+class XCTestFilesResolver {
+ public:
+  XCTestFilesResolver();
+  ~XCTestFilesResolver();
+
+  // Returns a set of all XCTest files for |target|. The returned reference
+  // may be invalidated the next time this method is called.
+  const SourceFileSet& SearchFilesForTarget(const Target* target);
+
+ private:
+  std::map<const Target*, SourceFileSet> cache_;
+};
+
+XCTestFilesResolver::XCTestFilesResolver() = default;
+
+XCTestFilesResolver::~XCTestFilesResolver() = default;
+
+const SourceFileSet& XCTestFilesResolver::SearchFilesForTarget(
+    const Target* target) {
+  // Early return if already visited and processed.
+  auto iter = cache_.find(target);
+  if (iter != cache_.end())
+    return iter->second;
+
+  SourceFileSet xctest_files;
+  for (const SourceFile& file : target->sources()) {
+    if (IsXCTestFile(file)) {
+      xctest_files.insert(file);
+    }
+  }
+
+  // Call recursively on public and private deps.
+  for (const auto& t : target->public_deps()) {
+    const SourceFileSet& deps_xctest_files = SearchFilesForTarget(t.ptr);
+    xctest_files.insert(deps_xctest_files.begin(), deps_xctest_files.end());
+  }
+
+  for (const auto& t : target->private_deps()) {
+    const SourceFileSet& deps_xctest_files = SearchFilesForTarget(t.ptr);
+    xctest_files.insert(deps_xctest_files.begin(), deps_xctest_files.end());
+  }
+
+  auto insert = cache_.insert(std::make_pair(target, xctest_files));
+  DCHECK(insert.second);
+  return insert.first->second;
+}
+
+// Add xctest files to the "Compiler Sources" of corresponding test module
+// native targets.
+void AddXCTestFilesToTestModuleTarget(const std::vector<SourceFile>& sources,
+                                      PBXNativeTarget* native_target,
+                                      PBXProject* project,
+                                      SourceDir source_dir,
+                                      const BuildSettings* build_settings) {
+  for (const SourceFile& source : sources) {
+    const std::string source_path = RebasePath(
+        source.value(), source_dir, build_settings->root_path_utf8());
+    project->AddSourceFile(source_path, source_path, native_target);
+  }
+}
+
+// Helper class to collect all PBXObject per class.
+class CollectPBXObjectsPerClassHelper : public PBXObjectVisitorConst {
+ public:
+  CollectPBXObjectsPerClassHelper() = default;
+
+  void Visit(const PBXObject* object) override {
+    DCHECK(object);
+    objects_per_class_[object->Class()].push_back(object);
+  }
+
+  const std::map<PBXObjectClass, std::vector<const PBXObject*>>&
+  objects_per_class() const {
+    return objects_per_class_;
+  }
+
+ private:
+  std::map<PBXObjectClass, std::vector<const PBXObject*>> objects_per_class_;
+
+  DISALLOW_COPY_AND_ASSIGN(CollectPBXObjectsPerClassHelper);
+};
+
+std::map<PBXObjectClass, std::vector<const PBXObject*>>
+CollectPBXObjectsPerClass(const PBXProject* project) {
+  CollectPBXObjectsPerClassHelper visitor;
+  project->Visit(visitor);
+  return visitor.objects_per_class();
+}
+
+// Helper class to assign unique ids to all PBXObject.
+class RecursivelyAssignIdsHelper : public PBXObjectVisitor {
+ public:
+  RecursivelyAssignIdsHelper(const std::string& seed)
+      : seed_(seed), counter_(0) {}
+
+  void Visit(PBXObject* object) override {
+    std::stringstream buffer;
+    buffer << seed_ << " " << object->Name() << " " << counter_;
+    std::string hash = base::SHA1HashString(buffer.str());
+    DCHECK_EQ(hash.size() % 4, 0u);
+
+    uint32_t id[3] = {0, 0, 0};
+    const uint32_t* ptr = reinterpret_cast<const uint32_t*>(hash.data());
+    for (size_t i = 0; i < hash.size() / 4; i++)
+      id[i % 3] ^= ptr[i];
+
+    object->SetId(base::HexEncode(id, sizeof(id)));
+    ++counter_;
+  }
+
+ private:
+  std::string seed_;
+  int64_t counter_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecursivelyAssignIdsHelper);
+};
+
+void RecursivelyAssignIds(PBXProject* project) {
+  RecursivelyAssignIdsHelper visitor(project->Name());
+  project->Visit(visitor);
+}
+
+// Returns a configuration name derived from the build directory. This gives
+// standard names if using the Xcode convention of naming the build directory
+// out/$configuration-$platform (e.g. out/Debug-iphonesimulator).
+std::string ConfigNameFromBuildSettings(const BuildSettings* build_settings) {
+  std::string config_name = FilePathToUTF8(build_settings->build_dir()
+                                               .Resolve(base::FilePath())
+                                               .StripTrailingSeparators()
+                                               .BaseName());
+
+  std::string::size_type separator = config_name.find('-');
+  if (separator != std::string::npos)
+    config_name = config_name.substr(0, separator);
+
+  DCHECK(!config_name.empty());
+  return config_name;
+}
+
+// Returns the path to root_src_dir from settings.
+std::string SourcePathFromBuildSettings(const BuildSettings* build_settings) {
+  return RebasePath("//", build_settings->build_dir());
+}
+
+// Returns the default attributes for the project from settings.
+PBXAttributes ProjectAttributesFromBuildSettings(
+    const BuildSettings* build_settings) {
+  const TargetOsType target_os = GetTargetOs(build_settings->build_args());
+
+  PBXAttributes attributes;
+  switch (target_os) {
+    case WRITER_TARGET_OS_IOS:
+      attributes["SDKROOT"] = "iphoneos";
+      attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
+      break;
+    case WRITER_TARGET_OS_MACOS:
+      attributes["SDKROOT"] = "macosx";
+      break;
+  }
+
+  // Xcode complains that the project needs to be upgraded if those keys are
+  // not set. Since the generated Xcode project is only used for debugging
+  // and the source of truth for build settings is the .gn files themselves,
+  // we can safely set them in the project as they won't be used by "ninja".
+  attributes["ALWAYS_SEARCH_USER_PATHS"] = "NO";
+  attributes["CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED"] = "YES";
+  attributes["CLANG_WARN__DUPLICATE_METHOD_MATCH"] = "YES";
+  attributes["CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING"] = "YES";
+  attributes["CLANG_WARN_BOOL_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_COMMA"] = "YES";
+  attributes["CLANG_WARN_CONSTANT_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS"] = "YES";
+  attributes["CLANG_WARN_EMPTY_BODY"] = "YES";
+  attributes["CLANG_WARN_ENUM_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_INFINITE_RECURSION"] = "YES";
+  attributes["CLANG_WARN_INT_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_NON_LITERAL_NULL_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF"] = "YES";
+  attributes["CLANG_WARN_OBJC_LITERAL_CONVERSION"] = "YES";
+  attributes["CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER"] = "YES";
+  attributes["CLANG_WARN_RANGE_LOOP_ANALYSIS"] = "YES";
+  attributes["CLANG_WARN_STRICT_PROTOTYPES"] = "YES";
+  attributes["CLANG_WARN_SUSPICIOUS_MOVE"] = "YES";
+  attributes["CLANG_WARN_UNREACHABLE_CODE"] = "YES";
+  attributes["ENABLE_STRICT_OBJC_MSGSEND"] = "YES";
+  attributes["ENABLE_TESTABILITY"] = "YES";
+  attributes["GCC_NO_COMMON_BLOCKS"] = "YES";
+  attributes["GCC_WARN_64_TO_32_BIT_CONVERSION"] = "YES";
+  attributes["GCC_WARN_ABOUT_RETURN_TYPE"] = "YES";
+  attributes["GCC_WARN_UNDECLARED_SELECTOR"] = "YES";
+  attributes["GCC_WARN_UNINITIALIZED_AUTOS"] = "YES";
+  attributes["GCC_WARN_UNUSED_FUNCTION"] = "YES";
+  attributes["GCC_WARN_UNUSED_VARIABLE"] = "YES";
+  attributes["ONLY_ACTIVE_ARCH"] = "YES";
+
+  return attributes;
+}
+
+}  // namespace
+
+// Class representing the workspace embedded in an xcodeproj file used to
+// configure the build settings shared by all targets in the project (used
+// to configure the build system).
+class XcodeWorkspace {
+ public:
+  XcodeWorkspace(const BuildSettings* build_settings,
+                 XcodeWriter::Options options);
+  ~XcodeWorkspace();
+
+  XcodeWorkspace(const XcodeWorkspace&) = delete;
+  XcodeWorkspace& operator=(const XcodeWorkspace&) = delete;
+
+  // Generates the .xcworkspace files to disk.
+  bool WriteWorkspace(const std::string& name, Err* err) const;
+
+ private:
+  // Writes the workspace data file.
+  bool WriteWorkspaceDataFile(const std::string& name, Err* err) const;
+
+  // Writes the settings file.
+  bool WriteSettingsFile(const std::string& name, Err* err) const;
+
+  const BuildSettings* build_settings_ = nullptr;
+  XcodeWriter::Options options_;
+};
+
+XcodeWorkspace::XcodeWorkspace(const BuildSettings* build_settings,
+                               XcodeWriter::Options options)
+    : build_settings_(build_settings), options_(options) {}
+
+XcodeWorkspace::~XcodeWorkspace() = default;
+
+bool XcodeWorkspace::WriteWorkspace(const std::string& name, Err* err) const {
+  return WriteWorkspaceDataFile(name, err) && WriteSettingsFile(name, err);
+}
+
+bool XcodeWorkspace::WriteWorkspaceDataFile(const std::string& name,
+                                            Err* err) const {
+  const SourceFile source_file =
+      build_settings_->build_dir().ResolveRelativeFile(
+          Value(nullptr, name + "/contents.xcworkspacedata"), err);
+  if (source_file.is_null())
+    return false;
+
+  std::stringstream out;
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      << "<Workspace\n"
+      << "   version = \"1.0\">\n"
+      << "   <FileRef\n"
+      << "      location = \"self:\">\n"
+      << "   </FileRef>\n"
+      << "</Workspace>\n";
+
+  return WriteFileIfChanged(build_settings_->GetFullPath(source_file),
+                            out.str(), err);
+}
+
+bool XcodeWorkspace::WriteSettingsFile(const std::string& name,
+                                       Err* err) const {
+  const SourceFile source_file =
+      build_settings_->build_dir().ResolveRelativeFile(
+          Value(nullptr, name + "/xcshareddata/WorkspaceSettings.xcsettings"),
+          err);
+  if (source_file.is_null())
+    return false;
+
+  std::stringstream out;
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+      << "<plist version=\"1.0\">\n"
+      << "<dict>\n";
+
+  switch (options_.build_system) {
+    case XcodeBuildSystem::kLegacy:
+      out << "\t<key>BuildSystemType</key>\n"
+          << "\t<string>Original</string>\n";
+      break;
+    case XcodeBuildSystem::kNew:
+      break;
+  }
+
+  out << "</dict>\n"
+      << "</plist>\n";
+
+  return WriteFileIfChanged(build_settings_->GetFullPath(source_file),
+                            out.str(), err);
+}
+
+// Class responsible for constructing and writing the .xcodeproj from the
+// targets known to gn. It currently requires using the "Legacy build system"
+// so it will embed an .xcworkspace file to force the setting.
+class XcodeProject {
+ public:
+  XcodeProject(const BuildSettings* build_settings,
+               XcodeWriter::Options options);
+  ~XcodeProject();
+
+  // Recursively finds "source" files from |builder| and adds them to the
+  // project (this includes more than just text source files, e.g. images
+  // in resources, ...).
+  bool AddSourcesFromBuilder(const Builder& builder, Err* err);
+
+  // Recursively finds targets from |builder| and adds them to the project.
+  // Only targets of type CREATE_BUNDLE or EXECUTABLE are kept since they
+  // are the only one that can be run and thus debugged from Xcode.
+  bool AddTargetsFromBuilder(const Builder& builder, Err* err);
+
+  // Assigns ids to all PBXObject that were added to the project. Must be
+  // called before calling WriteFile().
+  bool AssignIds(Err* err);
+
+  // Generates the project file and the .xcodeproj file to disk if updated
+  // (i.e. if the generated project is identical to the currently existing
+  // one, it is not overwritten).
+  bool WriteFile(Err* err) const;
+
+ private:
+  // Finds all targets that needs to be generated for the project (applies
+  // the filter passed via |options|).
+  std::optional<std::vector<const Target*>> GetTargetsFromBuilder(
+      const Builder& builder,
+      Err* err) const;
+
+  // Adds a target of type EXECUTABLE to the project.
+  PBXNativeTarget* AddBinaryTarget(const Target* target,
+                                   base::Environment* env,
+                                   Err* err);
+
+  // Adds a target of type CREATE_BUNDLE to the project.
+  PBXNativeTarget* AddBundleTarget(const Target* target,
+                                   base::Environment* env,
+                                   Err* err);
+
+  // Adds the XCTest source files for all test xctest or xcuitest module target
+  // to allow Xcode to index the list of tests (thus allowing to run individual
+  // tests from Xcode UI).
+  bool AddCXTestSourceFilesForTestModuleTargets(
+      const std::map<const Target*, PBXNativeTarget*>& bundle_targets,
+      Err* err);
+
+  // Adds the corresponding test application target as dependency of xctest or
+  // xcuitest module target in the generated Xcode project.
+  bool AddDependencyTargetsForTestModuleTargets(
+      const std::map<const Target*, PBXNativeTarget*>& bundle_targets,
+      Err* err);
+
+  // Generates the content of the .xcodeproj file into |out|.
+  void WriteFileContent(std::ostream& out) const;
+
+  // Returns whether the file should be added to the project.
+  bool ShouldIncludeFileInProject(const SourceFile& source) const;
+
+  const BuildSettings* build_settings_;
+  XcodeWriter::Options options_;
+  PBXProject project_;
+};
+
+XcodeProject::XcodeProject(const BuildSettings* build_settings,
+                           XcodeWriter::Options options)
+    : build_settings_(build_settings),
+      options_(options),
+      project_(options.project_name,
+               ConfigNameFromBuildSettings(build_settings),
+               SourcePathFromBuildSettings(build_settings),
+               ProjectAttributesFromBuildSettings(build_settings)) {}
+
+XcodeProject::~XcodeProject() = default;
+
+bool XcodeProject::ShouldIncludeFileInProject(const SourceFile& source) const {
+  if (IsStringInOutputDir(build_settings_->build_dir(), source.value()))
+    return false;
+
+  if (IsPathAbsolute(source.value()))
+    return false;
+
+  return true;
+}
+
+bool XcodeProject::AddSourcesFromBuilder(const Builder& builder, Err* err) {
+  SourceFileSet sources;
+
+  // Add sources from all targets.
+  for (const Target* target : builder.GetAllResolvedTargets()) {
+    for (const SourceFile& source : target->sources()) {
+      if (ShouldIncludeFileInProject(source))
+        sources.insert(source);
+    }
+
+    for (const SourceFile& source : target->config_values().inputs()) {
+      if (ShouldIncludeFileInProject(source))
+        sources.insert(source);
+    }
+
+    for (const SourceFile& source : target->public_headers()) {
+      if (ShouldIncludeFileInProject(source))
+        sources.insert(source);
+    }
+
+    if (target->output_type() == Target::ACTION ||
+        target->output_type() == Target::ACTION_FOREACH) {
+      if (ShouldIncludeFileInProject(target->action_values().script()))
+        sources.insert(target->action_values().script());
+    }
+  }
+
+  // Add BUILD.gn and *.gni for targets, configs and toolchains.
+  for (const Item* item : builder.GetAllResolvedItems()) {
+    if (!item->AsConfig() && !item->AsTarget() && !item->AsToolchain())
+      continue;
+
+    const SourceFile build = builder.loader()->BuildFileForLabel(item->label());
+    if (ShouldIncludeFileInProject(build))
+      sources.insert(build);
+
+    for (const SourceFile& source :
+         item->settings()->import_manager().GetImportedFiles()) {
+      if (ShouldIncludeFileInProject(source))
+        sources.insert(source);
+    }
+  }
+
+  // Add other files read by gn (the main dotfile, exec_script scripts, ...).
+  for (const auto& path : g_scheduler->GetGenDependencies()) {
+    if (!build_settings_->root_path().IsParent(path))
+      continue;
+
+    const std::string as8bit = path.As8Bit();
+    const SourceFile source(
+        "//" + as8bit.substr(build_settings_->root_path().value().size() + 1));
+
+    if (ShouldIncludeFileInProject(source))
+      sources.insert(source);
+  }
+
+  // Sort files to ensure deterministic generation of the project file (and
+  // nicely sorted file list in Xcode).
+  std::vector<SourceFile> sorted_sources(sources.begin(), sources.end());
+  std::sort(sorted_sources.begin(), sorted_sources.end());
+
+  const SourceDir source_dir("//");
+  for (const SourceFile& source : sorted_sources) {
+    const std::string source_file = RebasePath(
+        source.value(), source_dir, build_settings_->root_path_utf8());
+    project_.AddSourceFileToIndexingTarget(source_file, source_file);
+  }
+
+  return true;
+}
+
+bool XcodeProject::AddTargetsFromBuilder(const Builder& builder, Err* err) {
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+
+  const std::string root_src_dir =
+      RebasePath("//", build_settings_->build_dir());
+  project_.AddAggregateTarget("All", GetBuildScript(options_.root_target_name,
+                                                    options_.ninja_executable,
+                                                    root_src_dir, env.get()));
+
+  const std::optional<std::vector<const Target*>> targets =
+      GetTargetsFromBuilder(builder, err);
+  if (!targets)
+    return false;
+
+  std::map<const Target*, PBXNativeTarget*> bundle_targets;
+
+  const TargetOsType target_os = GetTargetOs(build_settings_->build_args());
+
+  for (const Target* target : *targets) {
+    PBXNativeTarget* native_target = nullptr;
+    switch (target->output_type()) {
+      case Target::EXECUTABLE:
+        if (target_os == WRITER_TARGET_OS_IOS)
+          continue;
+
+        native_target = AddBinaryTarget(target, env.get(), err);
+        if (!native_target)
+          return false;
+
+        break;
+
+      case Target::CREATE_BUNDLE: {
+        if (target->bundle_data().product_type().empty())
+          continue;
+
+        // For XCUITest, two CREATE_BUNDLE targets are generated:
+        // ${target_name}_runner and ${target_name}_module, however, Xcode
+        // requires only one target named ${target_name} to run tests.
+        if (IsXCUITestRunnerTarget(target))
+          continue;
+
+        native_target = AddBundleTarget(target, env.get(), err);
+        if (!native_target)
+          return false;
+
+        bundle_targets.insert(std::make_pair(target, native_target));
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+  if (!AddCXTestSourceFilesForTestModuleTargets(bundle_targets, err))
+    return false;
+
+  // Adding the corresponding test application target as a dependency of xctest
+  // or xcuitest module target in the generated Xcode project so that the
+  // application target is re-compiled when compiling the test module target.
+  if (!AddDependencyTargetsForTestModuleTargets(bundle_targets, err))
+    return false;
+
+  return true;
+}
+
+bool XcodeProject::AddCXTestSourceFilesForTestModuleTargets(
+    const std::map<const Target*, PBXNativeTarget*>& bundle_targets,
+    Err* err) {
+  const SourceDir source_dir("//");
+
+  // Needs to search for xctest files under the application targets, and this
+  // variable is used to store the results of visited targets, thus making the
+  // search more efficient.
+  XCTestFilesResolver resolver;
+
+  for (const auto& pair : bundle_targets) {
+    const Target* target = pair.first;
+    if (!IsXCTestModuleTarget(target) && !IsXCUITestModuleTarget(target))
+      continue;
+
+    // For XCTest, test files are compiled into the application bundle.
+    // For XCUITest, test files are compiled into the test module bundle.
+    const Target* target_with_xctest_files = nullptr;
+    if (IsXCTestModuleTarget(target)) {
+      auto app_pair = FindApplicationTargetByName(
+          target->defined_from(),
+          target->bundle_data().xcode_test_application_name(), bundle_targets,
+          err);
+      if (!app_pair)
+        return false;
+      target_with_xctest_files = app_pair.value().first;
+    } else {
+      DCHECK(IsXCUITestModuleTarget(target));
+      target_with_xctest_files = target;
+    }
+
+    const SourceFileSet& sources =
+        resolver.SearchFilesForTarget(target_with_xctest_files);
+
+    // Sort files to ensure deterministic generation of the project file (and
+    // nicely sorted file list in Xcode).
+    std::vector<SourceFile> sorted_sources(sources.begin(), sources.end());
+    std::sort(sorted_sources.begin(), sorted_sources.end());
+
+    // Add xctest files to the "Compiler Sources" of corresponding xctest
+    // and xcuitest native targets for proper indexing and for discovery of
+    // tests function.
+    AddXCTestFilesToTestModuleTarget(sorted_sources, pair.second, &project_,
+                                     source_dir, build_settings_);
+  }
+
+  return true;
+}
+
+bool XcodeProject::AddDependencyTargetsForTestModuleTargets(
+    const std::map<const Target*, PBXNativeTarget*>& bundle_targets,
+    Err* err) {
+  for (const auto& pair : bundle_targets) {
+    const Target* target = pair.first;
+    if (!IsXCTestModuleTarget(target) && !IsXCUITestModuleTarget(target))
+      continue;
+
+    auto app_pair = FindApplicationTargetByName(
+        target->defined_from(),
+        target->bundle_data().xcode_test_application_name(), bundle_targets,
+        err);
+    if (!app_pair)
+      return false;
+
+    AddPBXTargetDependency(app_pair.value().second, pair.second, &project_);
+  }
+
+  return true;
+}
+
+bool XcodeProject::AssignIds(Err* err) {
+  RecursivelyAssignIds(&project_);
+  return true;
+}
+
+bool XcodeProject::WriteFile(Err* err) const {
+  DCHECK(!project_.id().empty());
+
+  SourceFile pbxproj_file = build_settings_->build_dir().ResolveRelativeFile(
+      Value(nullptr, project_.Name() + ".xcodeproj/project.pbxproj"), err);
+  if (pbxproj_file.is_null())
+    return false;
+
+  std::stringstream pbxproj_string_out;
+  WriteFileContent(pbxproj_string_out);
+
+  if (!WriteFileIfChanged(build_settings_->GetFullPath(pbxproj_file),
+                          pbxproj_string_out.str(), err)) {
+    return false;
+  }
+
+  XcodeWorkspace workspace(build_settings_, options_);
+  return workspace.WriteWorkspace(
+      project_.Name() + ".xcodeproj/project.xcworkspace", err);
+}
+
+std::optional<std::vector<const Target*>> XcodeProject::GetTargetsFromBuilder(
+    const Builder& builder,
+    Err* err) const {
+  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+
+  // Filter targets according to the dir_filters_string if defined.
+  if (!options_.dir_filters_string.empty()) {
+    std::vector<LabelPattern> filters;
+    if (!commands::FilterPatternsFromString(
+            build_settings_, options_.dir_filters_string, &filters, err)) {
+      return std::nullopt;
+    }
+
+    std::vector<const Target*> unfiltered_targets;
+    std::swap(unfiltered_targets, all_targets);
+
+    commands::FilterTargetsByPatterns(unfiltered_targets, filters,
+                                      &all_targets);
+  }
+
+  // Filter out all target of type EXECUTABLE that are direct dependency of
+  // a BUNDLE_DATA target (under the assumption that they will be part of a
+  // CREATE_BUNDLE target generating an application bundle).
+  std::set<const Target*> targets(all_targets.begin(), all_targets.end());
+  for (const Target* target : all_targets) {
+    if (!target->settings()->is_default())
+      continue;
+
+    if (target->output_type() != Target::BUNDLE_DATA)
+      continue;
+
+    for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+      if (pair.ptr->output_type() != Target::EXECUTABLE)
+        continue;
+
+      auto iter = targets.find(pair.ptr);
+      if (iter != targets.end())
+        targets.erase(iter);
+    }
+  }
+
+  // Sort the list of targets per-label to get a consistent ordering of them
+  // in the generated Xcode project (and thus stability of the file generated).
+  std::vector<const Target*> sorted_targets(targets.begin(), targets.end());
+  std::sort(sorted_targets.begin(), sorted_targets.end(),
+            [](const Target* lhs, const Target* rhs) {
+              return lhs->label() < rhs->label();
+            });
+
+  return sorted_targets;
+}
+
+PBXNativeTarget* XcodeProject::AddBinaryTarget(const Target* target,
+                                               base::Environment* env,
+                                               Err* err) {
+  DCHECK_EQ(target->output_type(), Target::EXECUTABLE);
+
+  std::string output_dir = target->output_dir().value();
+  if (output_dir.empty()) {
+    const Tool* tool = target->toolchain()->GetToolForTargetFinalOutput(target);
+    if (!tool) {
+      std::string tool_name = Tool::GetToolTypeForTargetFinalOutput(target);
+      *err = Err(nullptr, tool_name + " tool not defined",
+                 "The toolchain " +
+                     target->toolchain()->label().GetUserVisibleName(false) +
+                     " used by target " +
+                     target->label().GetUserVisibleName(false) +
+                     " doesn't define a \"" + tool_name + "\" tool.");
+      return nullptr;
+    }
+    output_dir = SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                     target, tool, tool->default_output_dir())
+                     .value();
+  } else {
+    output_dir = RebasePath(output_dir, build_settings_->build_dir());
+  }
+
+  const std::string root_src_dir =
+      RebasePath("//", build_settings_->build_dir());
+  return project_.AddNativeTarget(
+      target->label().name(), "compiled.mach-o.executable",
+      target->output_name().empty() ? target->label().name()
+                                    : target->output_name(),
+      "com.apple.product-type.tool", output_dir,
+      GetBuildScript(target->label().name(), options_.ninja_executable,
+                     root_src_dir, env));
+}
+
+PBXNativeTarget* XcodeProject::AddBundleTarget(const Target* target,
+                                               base::Environment* env,
+                                               Err* err) {
+  DCHECK_EQ(target->output_type(), Target::CREATE_BUNDLE);
+
+  std::string pbxtarget_name = target->label().name();
+  if (IsXCUITestModuleTarget(target)) {
+    std::string target_name = target->label().name();
+    pbxtarget_name = target_name.substr(
+        0, target_name.rfind(kXCTestModuleTargetNamePostfix));
+  }
+
+  PBXAttributes xcode_extra_attributes =
+      target->bundle_data().xcode_extra_attributes();
+  if (options_.build_system == XcodeBuildSystem::kLegacy) {
+    xcode_extra_attributes["CODE_SIGN_IDENTITY"] = "";
+  }
+
+  const std::string& target_output_name = RebasePath(
+      target->bundle_data().GetBundleRootDirOutput(target->settings()).value(),
+      build_settings_->build_dir());
+  const std::string output_dir = RebasePath(target->bundle_data()
+          .GetBundleDir(target->settings())
+          .value(),
+      build_settings_->build_dir());
+  const std::string root_src_dir =
+      RebasePath("//", build_settings_->build_dir());
+  return project_.AddNativeTarget(
+      pbxtarget_name, std::string(), target_output_name,
+      target->bundle_data().product_type(), output_dir,
+      GetBuildScript(pbxtarget_name, options_.ninja_executable, root_src_dir,
+                     env),
+      xcode_extra_attributes);
+}
+
+void XcodeProject::WriteFileContent(std::ostream& out) const {
+  out << "// !$*UTF8*$!\n"
+      << "{\n"
+      << "\tarchiveVersion = 1;\n"
+      << "\tclasses = {\n"
+      << "\t};\n"
+      << "\tobjectVersion = 46;\n"
+      << "\tobjects = {\n";
+
+  for (auto& pair : CollectPBXObjectsPerClass(&project_)) {
+    out << "\n"
+        << "/* Begin " << ToString(pair.first) << " section */\n";
+    std::sort(pair.second.begin(), pair.second.end(),
+              [](const PBXObject* a, const PBXObject* b) {
+                return a->id() < b->id();
+              });
+    for (auto* object : pair.second) {
+      object->Print(out, 2);
+    }
+    out << "/* End " << ToString(pair.first) << " section */\n";
+  }
+
+  out << "\t};\n"
+      << "\trootObject = " << project_.Reference() << ";\n"
+      << "}\n";
+}
+
+// static
+bool XcodeWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+                                   const Builder& builder,
+                                   Options options,
+                                   Err* err) {
+  XcodeProject project(build_settings, options);
+  if (!project.AddSourcesFromBuilder(builder, err))
+    return false;
+
+  if (!project.AddTargetsFromBuilder(builder, err))
+    return false;
+
+  if (!project.AssignIds(err))
+    return false;
+
+  if (!project.WriteFile(err))
+    return false;
+
+  return true;
+}
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
new file mode 100644 (file)
index 0000000..f934275
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_XCODE_WRITER_H_
+#define TOOLS_GN_XCODE_WRITER_H_
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class Builder;
+class BuildSettings;
+class Err;
+
+enum class XcodeBuildSystem {
+  kLegacy,
+  kNew,
+};
+
+// Writes an Xcode workspace to build and debug code.
+class XcodeWriter {
+ public:
+  // Controls some parameters and behaviour of the RunAndWriteFiles().
+  struct Options {
+    // Name of the generated project file. Defaults to "all" is empty.
+    std::string project_name;
+
+    // Name of the ninja target to use for the "All" target in the generated
+    // project. If empty, no target will be passed to ninja which will thus
+    // try to build all defined targets.
+    std::string root_target_name;
+
+    // Name of the ninja executable. Defaults to "ninja" if empty.
+    std::string ninja_executable;
+
+    // If specified, should be a semicolon-separated list of label patterns.
+    // It will be used to filter the list of targets generated in the project
+    // (in the same way that the other filtering is done, source and header
+    // files for those target will still be listed in the generated project).
+    std::string dir_filters_string;
+
+    // Control which version of the build system should be used for the
+    // generated Xcode project.
+    XcodeBuildSystem build_system = XcodeBuildSystem::kLegacy;
+  };
+
+  // Writes an Xcode workspace with a single project file.
+  //
+  // The project will lists all files referenced for the build (including the
+  // sources, headers and some supporting files). The project can be used to
+  // build, develop and debug from Xcode (though adding files, changing build
+  // settings, etc. still needs to be done via BUILD.gn files).
+  //
+  // The list of targets is filtered to only include relevant targets for
+  // debugging (mostly binaries and bundles) so it is not possible to build
+  // individuals targets (i.e. source_set) via Xcode. This filtering is done
+  // to improve the performances when loading the solution in Xcode (project
+  // like Chromium cannot be opened if all targets are generated).
+  //
+  // The source and header files are still listed in the generated generated
+  // Xcode project, even if the target they are defined in are filtered (not
+  // doing so would make it less pleasant to use Xcode to debug without any
+  // significant performance improvement).
+  //
+  // Extra behaviour is controlled by the |options| parameter. See comments
+  // of the Options type for more informations.
+  //
+  // Returns true on success, fails on failure. |err| is set in that case.
+  static bool RunAndWriteFiles(const BuildSettings* build_settings,
+                               const Builder& builder,
+                               Options options,
+                               Err* err);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(XcodeWriter);
+};
+
+#endif  // TOOLS_GN_XCODE_WRITER_H_
diff --git a/src/gn/xml_element_writer.cc b/src/gn/xml_element_writer.cc
new file mode 100644 (file)
index 0000000..8525057
--- /dev/null
@@ -0,0 +1,114 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/xml_element_writer.h"
+
+#include <memory>
+
+XmlAttributes::XmlAttributes() = default;
+
+XmlAttributes::XmlAttributes(const std::string_view& attr_key,
+                             const std::string_view& attr_value) {
+  add(attr_key, attr_value);
+}
+
+XmlAttributes& XmlAttributes::add(const std::string_view& attr_key,
+                                  const std::string_view& attr_value) {
+  push_back(std::make_pair(attr_key, attr_value));
+  return *this;
+}
+
+XmlElementWriter::XmlElementWriter(std::ostream& out,
+                                   const std::string& tag,
+                                   const XmlAttributes& attributes)
+    : XmlElementWriter(out, tag, attributes, 0) {}
+
+XmlElementWriter::XmlElementWriter(std::ostream& out,
+                                   const std::string& tag,
+                                   const XmlAttributes& attributes,
+                                   int indent)
+    : out_(out),
+      tag_(tag),
+      indent_(indent),
+      opening_tag_finished_(false),
+      one_line_(true) {
+  out << std::string(indent, ' ') << '<' << tag;
+  for (auto attribute : attributes)
+    out << ' ' << attribute.first << "=\"" << attribute.second << '"';
+}
+
+XmlElementWriter::~XmlElementWriter() {
+  if (!opening_tag_finished_) {
+    // The XML spec does not require a space before the closing slash. However,
+    // Eclipse is unable to parse XML settings files if there is no space.
+    out_ << " />" << std::endl;
+  } else {
+    if (!one_line_)
+      out_ << std::string(indent_, ' ');
+    out_ << "</" << tag_ << '>' << std::endl;
+  }
+}
+
+void XmlElementWriter::Text(const std::string_view& content) {
+  StartContent(false);
+  out_ << content;
+}
+
+std::unique_ptr<XmlElementWriter> XmlElementWriter::SubElement(
+    const std::string& tag) {
+  return SubElement(tag, XmlAttributes());
+}
+
+std::unique_ptr<XmlElementWriter> XmlElementWriter::SubElement(
+    const std::string& tag,
+    const XmlAttributes& attributes) {
+  StartContent(true);
+  return std::make_unique<XmlElementWriter>(out_, tag, attributes, indent_ + 2);
+}
+
+std::ostream& XmlElementWriter::StartContent(bool start_new_line) {
+  if (!opening_tag_finished_) {
+    out_ << '>';
+    opening_tag_finished_ = true;
+
+    if (start_new_line && one_line_) {
+      out_ << std::endl;
+      one_line_ = false;
+    }
+  }
+
+  return out_;
+}
+
+std::string XmlEscape(const std::string& value) {
+  std::string result;
+  for (char c : value) {
+    switch (c) {
+      case '\n':
+        result += "&#10;";
+        break;
+      case '\r':
+        result += "&#13;";
+        break;
+      case '\t':
+        result += "&#9;";
+        break;
+      case '"':
+        result += "&quot;";
+        break;
+      case '<':
+        result += "&lt;";
+        break;
+      case '>':
+        result += "&gt;";
+        break;
+      case '&':
+        result += "&amp;";
+        break;
+      default:
+        result += c;
+    }
+  }
+  return result;
+}
diff --git a/src/gn/xml_element_writer.h b/src/gn/xml_element_writer.h
new file mode 100644 (file)
index 0000000..9040d16
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_XML_ELEMENT_WRITER_H_
+#define TOOLS_GN_XML_ELEMENT_WRITER_H_
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+
+// Vector of XML attribute key-value pairs.
+class XmlAttributes
+    : public std::vector<std::pair<std::string_view, std::string_view>> {
+ public:
+  XmlAttributes();
+  XmlAttributes(const std::string_view& attr_key,
+                const std::string_view& attr_value);
+
+  XmlAttributes& add(const std::string_view& attr_key,
+                     const std::string_view& attr_value);
+};
+
+// Helper class for writing XML elements. New XML element is started in
+// XmlElementWriter constructor and ended in its destructor. XmlElementWriter
+// handles XML file formatting in order to produce human-readable document.
+class XmlElementWriter {
+ public:
+  // Starts new XML element. This constructor adds no indentation and is
+  // designed for XML root element.
+  XmlElementWriter(std::ostream& out,
+                   const std::string& tag,
+                   const XmlAttributes& attributes);
+  // Starts new XML element with specified indentation.
+  XmlElementWriter(std::ostream& out,
+                   const std::string& tag,
+                   const XmlAttributes& attributes,
+                   int indent);
+  // Starts new XML element with specified indentation. Specialized constructor
+  // that allows writing XML element with single attribute without copying
+  // attribute value.
+  template <class Writer>
+  XmlElementWriter(std::ostream& out,
+                   const std::string& tag,
+                   const std::string& attribute_name,
+                   const Writer& attribute_value_writer,
+                   int indent);
+  // Ends XML element. All sub-elements should be ended at this point.
+  ~XmlElementWriter();
+
+  // Writes arbitrary XML element text.
+  void Text(const std::string_view& content);
+
+  // Starts new XML sub-element. Caller must ensure that parent element outlives
+  // its children.
+  std::unique_ptr<XmlElementWriter> SubElement(const std::string& tag);
+  std::unique_ptr<XmlElementWriter> SubElement(const std::string& tag,
+                                               const XmlAttributes& attributes);
+  template <class Writer>
+  std::unique_ptr<XmlElementWriter> SubElement(
+      const std::string& tag,
+      const std::string& attribute_name,
+      const Writer& attribute_value_writer);
+
+  // Finishes opening tag if it isn't finished yet and optionally starts new
+  // document line. Returns the stream where XML element content can be written.
+  // This is an alternative to Text() and SubElement() methods.
+  std::ostream& StartContent(bool start_new_line);
+
+ private:
+  // Output stream. XmlElementWriter objects for XML element and its
+  // sub-elements share the same output stream.
+  std::ostream& out_;
+
+  // XML element tag name.
+  std::string tag_;
+
+  // XML element indentation in the document.
+  int indent_;
+
+  // Flag indicating if opening tag is finished with '>' character already.
+  bool opening_tag_finished_;
+
+  // Flag indicating if XML element should be written in one document line.
+  bool one_line_;
+
+  DISALLOW_COPY_AND_ASSIGN(XmlElementWriter);
+};
+
+template <class Writer>
+XmlElementWriter::XmlElementWriter(std::ostream& out,
+                                   const std::string& tag,
+                                   const std::string& attribute_name,
+                                   const Writer& attribute_value_writer,
+                                   int indent)
+    : out_(out),
+      tag_(tag),
+      indent_(indent),
+      opening_tag_finished_(false),
+      one_line_(true) {
+  out << std::string(indent, ' ') << '<' << tag;
+  out << ' ' << attribute_name << "=\"";
+  attribute_value_writer(out);
+  out << '\"';
+}
+
+template <class Writer>
+std::unique_ptr<XmlElementWriter> XmlElementWriter::SubElement(
+    const std::string& tag,
+    const std::string& attribute_name,
+    const Writer& attribute_value_writer) {
+  StartContent(true);
+  return std::make_unique<XmlElementWriter>(
+      out_, tag, attribute_name, attribute_value_writer, indent_ + 2);
+}
+
+std::string XmlEscape(const std::string& value);
+
+#endif  // TOOLS_GN_XML_ELEMENT_WRITER_H_
diff --git a/src/gn/xml_element_writer_unittest.cc b/src/gn/xml_element_writer_unittest.cc
new file mode 100644 (file)
index 0000000..6a4cfed
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/xml_element_writer.h"
+
+#include <sstream>
+
+#include "util/test/test.h"
+
+namespace {
+
+class MockValueWriter {
+ public:
+  explicit MockValueWriter(const std::string& value) : value_(value) {}
+  void operator()(std::ostream& out) const { out << value_; }
+
+ private:
+  std::string value_;
+};
+
+}  // namespace
+
+TEST(XmlElementWriter, EmptyElement) {
+  std::ostringstream out;
+  { XmlElementWriter writer(out, "foo", XmlAttributes()); }
+  EXPECT_EQ("<foo />\n", out.str());
+
+  std::ostringstream out_attr;
+  {
+    XmlElementWriter writer(out_attr, "foo",
+                            XmlAttributes("bar", "abc").add("baz", "123"));
+  }
+  EXPECT_EQ("<foo bar=\"abc\" baz=\"123\" />\n", out_attr.str());
+
+  std::ostringstream out_indent;
+  {
+    XmlElementWriter writer(out_indent, "foo", XmlAttributes("bar", "baz"), 2);
+  }
+  EXPECT_EQ("  <foo bar=\"baz\" />\n", out_indent.str());
+
+  std::ostringstream out_writer;
+  {
+    XmlElementWriter writer(out_writer, "foo", "bar", MockValueWriter("baz"),
+                            2);
+  }
+  EXPECT_EQ("  <foo bar=\"baz\" />\n", out_writer.str());
+}
+
+TEST(XmlElementWriter, ElementWithText) {
+  std::ostringstream out;
+  {
+    XmlElementWriter writer(out, "foo", XmlAttributes("bar", "baz"));
+    writer.Text("Hello world!");
+  }
+  EXPECT_EQ("<foo bar=\"baz\">Hello world!</foo>\n", out.str());
+}
+
+TEST(XmlElementWriter, SubElements) {
+  std::ostringstream out;
+  {
+    XmlElementWriter writer(out, "root", XmlAttributes("aaa", "000"));
+    writer.SubElement("foo", XmlAttributes());
+    writer.SubElement("bar", XmlAttributes("bbb", "111"))->Text("hello");
+    writer.SubElement("baz", "ccc", MockValueWriter("222"))
+        ->SubElement("grandchild");
+  }
+  std::string expected =
+      "<root aaa=\"000\">\n"
+      "  <foo />\n"
+      "  <bar bbb=\"111\">hello</bar>\n"
+      "  <baz ccc=\"222\">\n"
+      "    <grandchild />\n"
+      "  </baz>\n"
+      "</root>\n";
+  EXPECT_EQ(expected, out.str());
+}
+
+TEST(XmlElementWriter, StartContent) {
+  std::ostringstream out;
+  {
+    XmlElementWriter writer(out, "foo", XmlAttributes("bar", "baz"));
+    writer.StartContent(false) << "Hello world!";
+  }
+  EXPECT_EQ("<foo bar=\"baz\">Hello world!</foo>\n", out.str());
+}
+
+TEST(XmlElementWriter, TestXmlEscape) {
+  std::string input = "\r \n \t & < > \"";
+  std::string output = XmlEscape(input);
+  std::string expected = "&#13; &#10; &#9; &amp; &lt; &gt; &quot;";
+  EXPECT_EQ(expected, output);
+}
diff --git a/src/util/auto_reset_event.h b/src/util/auto_reset_event.h
new file mode 100644 (file)
index 0000000..5e040a8
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can
+// be found in the LICENSE file.
+
+#ifndef UTIL_AUTO_RESET_EVENT_H_
+#define UTIL_AUTO_RESET_EVENT_H_
+
+#include <atomic>
+
+#include "base/logging.h"
+#include "util/semaphore.h"
+
+// From http://preshing.com/20150316/semaphores-are-surprisingly-versatile/,
+// but using V8's Semaphore.
+class AutoResetEvent {
+ private:
+  // status_ == 1: Event object is signaled.
+  // status_ == 0: Event object is reset and no threads are waiting.
+  // status_ == -N: Event object is reset and N threads are waiting.
+  std::atomic<int> status_;
+  Semaphore semaphore_;
+
+ public:
+  AutoResetEvent() : status_(0), semaphore_(0) {}
+
+  void Signal() {
+    int old_status = status_.load(std::memory_order_relaxed);
+    // Increment status_ atomically via CAS loop.
+    for (;;) {
+      DCHECK_LE(old_status, 1);
+      int new_status = old_status < 1 ? old_status + 1 : 1;
+      if (status_.compare_exchange_weak(old_status, new_status,
+                                        std::memory_order_release,
+                                        std::memory_order_relaxed)) {
+        break;
+      }
+      // The compare-exchange failed, likely because another thread changed
+      // status_. old_status has been updated. Retry the CAS loop.
+    }
+    if (old_status < 0)
+      semaphore_.Signal();  // Release one waiting thread.
+  }
+
+  void Wait() {
+    int old_status = status_.fetch_sub(1, std::memory_order_acquire);
+    DCHECK_LE(old_status, 1);
+    if (old_status < 1) {
+      semaphore_.Wait();
+    }
+  }
+};
+
+#endif  // UTIL_AUTO_RESET_EVENT_H_
diff --git a/src/util/build_config.h b/src/util/build_config.h
new file mode 100644 (file)
index 0000000..667f009
--- /dev/null
@@ -0,0 +1,177 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file adds defines about the platform we're currently building on.
+//  Operating System:
+//    OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) /
+//    OS_CHROMEOS is set by the build system
+//  Compiler:
+//    COMPILER_MSVC / COMPILER_GCC
+//  Processor:
+//    ARCH_CPU_X86 / ARCH_CPU_X86_64 / ARCH_CPU_X86_FAMILY (X86 or X86_64)
+//    ARCH_CPU_32_BITS / ARCH_CPU_64_BITS
+
+#ifndef BUILD_BUILD_CONFIG_H_
+#define BUILD_BUILD_CONFIG_H_
+
+#if defined(ANDROID)
+#define OS_ANDROID 1
+#elif defined(__APPLE__)
+// only include TargetConditions after testing ANDROID as some android builds
+// on mac don't have this header available and it's not needed unless the target
+// is really mac/ios.
+#include <TargetConditionals.h>
+#define OS_MACOSX 1
+#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+#define OS_IOS 1
+#endif  // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+#elif defined(__linux__)
+#define OS_LINUX 1
+// include a system header to pull in features.h for glibc/uclibc macros.
+#include <unistd.h>
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+// we really are using glibc, not uClibc pretending to be glibc
+#define LIBC_GLIBC 1
+#endif
+#elif defined(__MSYS__)
+#define OS_MSYS 1
+#elif defined(_WIN32)
+#define OS_WIN 1
+#elif defined(__Fuchsia__)
+#define OS_FUCHSIA 1
+#elif defined(__FreeBSD__)
+#define OS_FREEBSD 1
+#elif defined(__NetBSD__)
+#define OS_NETBSD 1
+#elif defined(__OpenBSD__)
+#define OS_OPENBSD 1
+#elif defined(__sun)
+#define OS_SOLARIS 1
+#elif defined(__QNXNTO__)
+#define OS_QNX 1
+#elif defined(_AIX)
+#define OS_AIX 1
+#elif defined(__asmjs__)
+#define OS_ASMJS 1
+#elif defined(__HAIKU__)
+#define OS_HAIKU 1
+#else
+#error Please add support for your platform in build_config.h
+#endif
+// NOTE: Adding a new port? Please follow
+// https://chromium.googlesource.com/chromium/src/+/master/docs/new_port_policy.md
+
+// For access to standard BSD features, use OS_BSD instead of a
+// more specific macro.
+#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD)
+#define OS_BSD 1
+#endif
+
+// For access to standard POSIXish features, use OS_POSIX instead of a
+// more specific macro.
+#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) ||    \
+    defined(OS_FREEBSD) || defined(OS_LINUX) || defined(OS_MACOSX) || \
+    defined(OS_NACL) || defined(OS_NETBSD) || defined(OS_OPENBSD) ||  \
+    defined(OS_QNX) || defined(OS_SOLARIS) || defined(OS_HAIKU) || \
+    defined(OS_MSYS)
+#define OS_POSIX 1
+#endif
+
+// Use tcmalloc
+#if (defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID)) && \
+    !defined(NO_TCMALLOC)
+#define USE_TCMALLOC 1
+#endif
+
+// Compiler detection.
+#if defined(__GNUC__)
+#define COMPILER_GCC 1
+#elif defined(_MSC_VER)
+#define COMPILER_MSVC 1
+#else
+#error Please add support for your compiler in build_config.h
+#endif
+
+// Processor architecture detection.  For more info on what's defined, see:
+//   http://msdn.microsoft.com/en-us/library/b0084kay.aspx
+//   http://www.agner.org/optimize/calling_conventions.pdf
+//   or with gcc, run: "echo | gcc -E -dM -"
+#if defined(_M_X64) || defined(__x86_64__)
+#define ARCH_CPU_X86_FAMILY 1
+#define ARCH_CPU_X86_64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(_M_IX86) || defined(__i386__)
+#define ARCH_CPU_X86_FAMILY 1
+#define ARCH_CPU_X86 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__s390x__)
+#define ARCH_CPU_S390_FAMILY 1
+#define ARCH_CPU_S390X 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#elif defined(__s390__)
+#define ARCH_CPU_S390_FAMILY 1
+#define ARCH_CPU_S390 1
+#define ARCH_CPU_31_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__)
+#define ARCH_CPU_PPC64_FAMILY 1
+#define ARCH_CPU_PPC64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#elif defined(__PPC64__)
+#define ARCH_CPU_PPC64_FAMILY 1
+#define ARCH_CPU_PPC64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__ARMEL__)
+#define ARCH_CPU_ARM_FAMILY 1
+#define ARCH_CPU_ARMEL 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__aarch64__)
+#define ARCH_CPU_ARM_FAMILY 1
+#define ARCH_CPU_ARM64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__pnacl__) || defined(__asmjs__)
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__MIPSEL__)
+#if defined(__LP64__)
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS64EL 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#else
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPSEL 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#endif
+#elif defined(__MIPSEB__)
+#if defined(__LP64__)
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#else
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#endif
+#elif defined(__e2k__)
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__riscv) && (__riscv_xlen == 64)
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
+#else
+#error Please add support for your architecture in build_config.h
+#endif
+
+#endif  // BUILD_BUILD_CONFIG_H_
diff --git a/src/util/exe_path.cc b/src/util/exe_path.cc
new file mode 100644 (file)
index 0000000..b67318c
--- /dev/null
@@ -0,0 +1,119 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/exe_path.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "util/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <mach-o/dyld.h>
+#elif defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/win_util.h"
+#elif defined(OS_FREEBSD) || defined(OS_NETBSD)
+#include <limits.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#elif defined(OS_HAIKU)
+#include <OS.h>
+#include <image.h>
+#elif defined(OS_SOLARIS)
+#include <stdlib.h>
+#endif
+
+#if defined(OS_MACOSX)
+
+base::FilePath GetExePath() {
+  // Executable path can have relative references ("..") depending on
+  // how the app was launched.
+  uint32_t executable_length = 0;
+  _NSGetExecutablePath(NULL, &executable_length);
+  DCHECK_GT(executable_length, 1u);
+  std::string executable_path;
+  int rv = _NSGetExecutablePath(
+      base::WriteInto(&executable_path, executable_length), &executable_length);
+  DCHECK_EQ(rv, 0);
+
+  // _NSGetExecutablePath may return paths containing ./ or ../ which makes
+  // FilePath::DirName() work incorrectly, convert it to absolute path so that
+  // paths such as DIR_SOURCE_ROOT can work, since we expect absolute paths to
+  // be returned here.
+  return base::MakeAbsoluteFilePath(base::FilePath(executable_path));
+}
+
+#elif defined(OS_WIN)
+
+base::FilePath GetExePath() {
+  char16_t system_buffer[MAX_PATH];
+  system_buffer[0] = 0;
+  if (GetModuleFileName(NULL, base::ToWCharT(system_buffer), MAX_PATH) == 0) {
+    return base::FilePath();
+  }
+  return base::FilePath(system_buffer);
+}
+
+#elif defined(OS_FREEBSD)
+
+base::FilePath GetExePath() {
+  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+  char buf[PATH_MAX];
+  size_t buf_size = PATH_MAX;
+  if (sysctl(mib, 4, buf, &buf_size, nullptr, 0) == -1) {
+    return base::FilePath();
+  }
+  return base::FilePath(buf);
+}
+
+#elif defined(OS_NETBSD)
+
+base::FilePath GetExePath() {
+  int mib[] = {CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_PATHNAME};
+  char buf[PATH_MAX];
+  size_t buf_size = PATH_MAX;
+  if (sysctl(mib, 4, buf, &buf_size, nullptr, 0) == -1) {
+    return base::FilePath();
+  }
+  return base::FilePath(buf);
+}
+
+#elif defined(OS_HAIKU)
+
+base::FilePath GetExePath() {
+  image_info i_info;
+  int32 image_cookie = 0;
+  while (get_next_image_info(B_CURRENT_TEAM, &image_cookie, &i_info) == B_OK) {
+    if (i_info.type == B_APP_IMAGE) {
+      break;
+    }
+  }
+  return base::FilePath(std::string(i_info.name));
+}
+
+#elif defined(OS_SOLARIS)
+
+base::FilePath GetExePath() {
+  const char *raw = getexecname();
+  if (raw == NULL) {
+    return base::FilePath();
+  }
+  return base::FilePath(raw);
+}
+
+#else
+
+base::FilePath GetExePath() {
+  base::FilePath result;
+  const char kProcSelfExe[] = "/proc/self/exe";
+  if (!ReadSymbolicLink(base::FilePath(kProcSelfExe), &result)) {
+    NOTREACHED() << "Unable to resolve " << kProcSelfExe << ".";
+    return base::FilePath();
+  }
+  return result;
+}
+
+#endif
diff --git a/src/util/exe_path.h b/src/util/exe_path.h
new file mode 100644 (file)
index 0000000..0e1b8cb
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_EXE_PATH_H_
+#define UTIL_EXE_PATH_H_
+
+#include "base/files/file_path.h"
+
+base::FilePath GetExePath();
+
+#endif  // UTIL_EXE_PATH_H_
diff --git a/src/util/msg_loop.cc b/src/util/msg_loop.cc
new file mode 100644 (file)
index 0000000..1566532
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/msg_loop.h"
+
+#include "base/logging.h"
+
+namespace {
+
+thread_local MsgLoop* g_current;
+}
+
+MsgLoop::MsgLoop() {
+  DCHECK(g_current == nullptr);
+  g_current = this;
+}
+
+MsgLoop::~MsgLoop() {
+  DCHECK(g_current == this);
+  g_current = nullptr;
+}
+
+void MsgLoop::Run() {
+  while (!should_quit_) {
+    std::function<void()> task;
+    {
+      std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+      notifier_.wait(queue_lock, [this]() {
+        return (!task_queue_.empty()) || should_quit_;
+      });
+
+      if (should_quit_)
+        return;
+
+      task = std::move(task_queue_.front());
+      task_queue_.pop();
+    }
+
+    task();
+  }
+}
+
+void MsgLoop::PostQuit() {
+  PostTask([this]() { should_quit_ = true; });
+}
+
+void MsgLoop::PostTask(std::function<void()> work) {
+  {
+    std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+    task_queue_.emplace(std::move(work));
+  }
+
+  notifier_.notify_one();
+}
+
+void MsgLoop::RunUntilIdleForTesting() {
+  for (bool done = false; !done;) {
+    std::function<void()> task;
+    {
+      std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+      task = std::move(task_queue_.front());
+      task_queue_.pop();
+
+      if (task_queue_.empty())
+        done = true;
+    }
+
+    task();
+  }
+}
+
+MsgLoop* MsgLoop::Current() {
+  return g_current;
+}
diff --git a/src/util/msg_loop.h b/src/util/msg_loop.h
new file mode 100644 (file)
index 0000000..b6e1ec7
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_RUN_LOOP_H_
+#define UTIL_RUN_LOOP_H_
+
+#include "base/macros.h"
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <queue>
+
+class MsgLoop {
+ public:
+  MsgLoop();
+  ~MsgLoop();
+
+  // Blocks until PostQuit() is called, processing work items posted via
+  void Run();
+
+  // Schedules Run() to exit, but will not happen until other outstanding tasks
+  // complete. Can be called from any thread.
+  void PostQuit();
+
+  // Posts a work item to this queue. All items will be run on the thread from
+  // which Run() was called. Can be called from any thread.
+  void PostTask(std::function<void()> task);
+
+  // Run()s until the queue is empty. Should only be used (carefully) in tests.
+  void RunUntilIdleForTesting();
+
+  // Gets the MsgLoop for the thread from which it's called, or nullptr if
+  // there's no MsgLoop for the current thread.
+  static MsgLoop* Current();
+
+ private:
+  std::mutex queue_mutex_;
+  std::queue<std::function<void()>> task_queue_;
+  std::condition_variable notifier_;
+  bool should_quit_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(MsgLoop);
+};
+
+#endif  // UTIL_RUN_LOOP_H_
diff --git a/src/util/semaphore.cc b/src/util/semaphore.cc
new file mode 100644 (file)
index 0000000..afed45e
--- /dev/null
@@ -0,0 +1,91 @@
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Based on
+// https://cs.chromium.org/chromium/src/v8/src/base/platform/semaphore.cc
+
+#include "util/semaphore.h"
+
+#include "base/logging.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#if defined(OS_MACOSX)
+
+Semaphore::Semaphore(int count) {
+  native_handle_ = dispatch_semaphore_create(count);
+  DCHECK(native_handle_);
+}
+
+Semaphore::~Semaphore() {
+  dispatch_release(native_handle_);
+}
+
+void Semaphore::Signal() {
+  dispatch_semaphore_signal(native_handle_);
+}
+
+void Semaphore::Wait() {
+  dispatch_semaphore_wait(native_handle_, DISPATCH_TIME_FOREVER);
+}
+
+#elif defined(OS_POSIX)
+
+Semaphore::Semaphore(int count) {
+  DCHECK_GE(count, 0);
+  int result = sem_init(&native_handle_, 0, count);
+  DCHECK_EQ(0, result);
+}
+
+Semaphore::~Semaphore() {
+  int result = sem_destroy(&native_handle_);
+  DCHECK_EQ(0, result);
+}
+
+void Semaphore::Signal() {
+  int result = sem_post(&native_handle_);
+  // This check may fail with <libc-2.21, which we use on the try bots, if the
+  // semaphore is destroyed while sem_post is still executed. A work around is
+  // to extend the lifetime of the semaphore.
+  CHECK_EQ(0, result);
+}
+
+void Semaphore::Wait() {
+  while (true) {
+    int result = sem_wait(&native_handle_);
+    if (result == 0)
+      return;  // Semaphore was signalled.
+    // Signal caused spurious wakeup.
+    DCHECK_EQ(-1, result);
+    DCHECK_EQ(EINTR, errno);
+  }
+}
+
+#elif defined(OS_WIN)
+
+Semaphore::Semaphore(int count) {
+  DCHECK_GE(count, 0);
+  native_handle_ = ::CreateSemaphoreA(nullptr, count, 0x7FFFFFFF, nullptr);
+  DCHECK(native_handle_);
+}
+
+Semaphore::~Semaphore() {
+  BOOL result = CloseHandle(native_handle_);
+  DCHECK(result);
+}
+
+void Semaphore::Signal() {
+  LONG dummy;
+  BOOL result = ReleaseSemaphore(native_handle_, 1, &dummy);
+  DCHECK(result);
+}
+
+void Semaphore::Wait() {
+  DWORD result = WaitForSingleObject(native_handle_, INFINITE);
+  DCHECK(result == WAIT_OBJECT_0);
+}
+
+#endif
diff --git a/src/util/semaphore.h b/src/util/semaphore.h
new file mode 100644 (file)
index 0000000..2952cae
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Based on
+// https://cs.chromium.org/chromium/src/v8/src/base/platform/semaphore.h
+
+#ifndef UTIL_SEMAPHORE_H_
+#define UTIL_SEMAPHORE_H_
+
+#include "base/macros.h"
+#include "util/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_MACOSX)
+#include <dispatch/dispatch.h>
+#elif defined(OS_POSIX)
+#include <semaphore.h>
+#else
+#error Port.
+#endif
+
+class Semaphore {
+ public:
+  explicit Semaphore(int count);
+  ~Semaphore();
+
+  // Increments the semaphore counter.
+  void Signal();
+
+  // Decrements the semaphore counter if it is positive, or blocks until it
+  // becomes positive and then decrements the counter.
+  void Wait();
+
+#if defined(OS_MACOSX)
+  using NativeHandle = dispatch_semaphore_t;
+#elif defined(OS_POSIX)
+  using NativeHandle = sem_t;
+#elif defined(OS_WIN)
+  using NativeHandle = HANDLE;
+#endif
+
+  NativeHandle& native_handle() { return native_handle_; }
+  const NativeHandle& native_handle() const { return native_handle_; }
+
+ private:
+  NativeHandle native_handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(Semaphore);
+};
+
+#endif  // UTIL_SEMAPHORE_H_
diff --git a/src/util/sys_info.cc b/src/util/sys_info.cc
new file mode 100644 (file)
index 0000000..8124e07
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/sys_info.h"
+
+#include "base/logging.h"
+#include "util/build_config.h"
+
+#if defined(OS_POSIX)
+#include <sys/utsname.h>
+#include <unistd.h>
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+std::string OperatingSystemArchitecture() {
+#if defined(OS_POSIX)
+  struct utsname info;
+  if (uname(&info) < 0) {
+    NOTREACHED();
+    return std::string();
+  }
+  std::string arch(info.machine);
+  std::string os(info.sysname);
+  if (arch == "i386" || arch == "i486" || arch == "i586" || arch == "i686") {
+    arch = "x86";
+  } else if (arch == "i86pc") {
+    // Solaris and illumos systems report 'i86pc' (an Intel x86 PC) as their
+    // machine for both 32-bit and 64-bit x86 systems.  Considering the rarity
+    // of 32-bit systems at this point, it is safe to assume 64-bit.
+    arch = "x86_64";
+  } else if (arch == "amd64") {
+    arch = "x86_64";
+  } else if (os == "AIX" || os == "OS400") {
+    arch = "ppc64";
+  }
+  return arch;
+#elif defined(OS_WIN)
+  SYSTEM_INFO system_info = {};
+  ::GetNativeSystemInfo(&system_info);
+  switch (system_info.wProcessorArchitecture) {
+    case PROCESSOR_ARCHITECTURE_INTEL:
+      return "x86";
+    case PROCESSOR_ARCHITECTURE_AMD64:
+      return "x86_64";
+    case PROCESSOR_ARCHITECTURE_IA64:
+      return "ia64";
+  }
+  return std::string();
+#else
+#error
+#endif
+}
+
+int NumberOfProcessors() {
+#if defined(OS_POSIX)
+  // sysconf returns the number of "logical" (not "physical") processors on both
+  // Mac and Linux.  So we get the number of max available "logical" processors.
+  //
+  // Note that the number of "currently online" processors may be fewer than the
+  // returned value of NumberOfProcessors(). On some platforms, the kernel may
+  // make some processors offline intermittently, to save power when system
+  // loading is low.
+  //
+  // One common use case that needs to know the processor count is to create
+  // optimal number of threads for optimization. It should make plan according
+  // to the number of "max available" processors instead of "currently online"
+  // ones. The kernel should be smart enough to make all processors online when
+  // it has sufficient number of threads waiting to run.
+  long res = sysconf(_SC_NPROCESSORS_CONF);
+  if (res == -1) {
+    NOTREACHED();
+    return 1;
+  }
+
+  return static_cast<int>(res);
+#elif defined(OS_WIN)
+  return ::GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
+#else
+#error
+#endif
+}
diff --git a/src/util/sys_info.h b/src/util/sys_info.h
new file mode 100644 (file)
index 0000000..68133e1
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_SYS_INFO_H_
+#define UTIL_SYS_INFO_H_
+
+#include <string>
+
+std::string OperatingSystemArchitecture();
+int NumberOfProcessors();
+
+#endif  // UTIL_SYS_INFO_H_
diff --git a/src/util/test/gn_test.cc b/src/util/test/gn_test.cc
new file mode 100644 (file)
index 0000000..6e9b964
--- /dev/null
@@ -0,0 +1,182 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/command_line.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+namespace testing {
+Test* g_current_test;
+}  // namespace testing
+
+struct RegisteredTest {
+  testing::Test* (*factory)();
+  const char* name;
+  bool should_run;
+};
+
+// This can't be a vector because tests call RegisterTest from static
+// initializers and the order static initializers run it isn't specified. So
+// the vector constructor isn't guaranteed to run before all of the
+// RegisterTest() calls.
+static RegisteredTest tests[10000];
+static int ntests;
+
+void RegisterTest(testing::Test* (*factory)(), const char* name) {
+  tests[ntests].factory = factory;
+  tests[ntests++].name = name;
+}
+
+namespace {
+
+bool PatternMatchesString(const char* pattern, const char* str) {
+  switch (*pattern) {
+    case '\0':
+    case '-':
+    case ':':
+      return *str == '\0';
+    case '*':
+      return (*str != '\0' && PatternMatchesString(pattern, str + 1)) ||
+             PatternMatchesString(pattern + 1, str);
+    default:
+      return *pattern == *str && PatternMatchesString(pattern + 1, str + 1);
+  }
+}
+
+bool PatternListMatchString(const char* pattern, const char* str) {
+  const char* const colon = strchr(pattern, ':');
+  if (PatternMatchesString(pattern, str))
+    return true;
+
+  if (!colon)
+    return false;
+
+  return PatternListMatchString(colon + 1, str);
+}
+
+bool TestMatchesFilter(const char* test, const char* filter) {
+  // Split --gtest_filter at '-' into positive and negative filters.
+  const char* const dash = strchr(filter, '-');
+  const char* pos =
+      dash == filter ? "*" : filter;  // Treat '-test1' as '*-test1'
+  const char* neg = dash ? dash + 1 : "";
+  return PatternListMatchString(pos, test) &&
+         !PatternListMatchString(neg, test);
+}
+
+#if defined(OS_WIN)
+struct ScopedEnableVTEscapeProcessing {
+  ScopedEnableVTEscapeProcessing() {
+    console_ = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    if (GetConsoleScreenBufferInfo(console_, &csbi) &&
+        GetConsoleMode(console_, &original_mode_)) {
+      SetConsoleMode(console_, original_mode_ |
+                                   ENABLE_VIRTUAL_TERMINAL_PROCESSING |
+                                   DISABLE_NEWLINE_AUTO_RETURN);
+    } else {
+      console_ = INVALID_HANDLE_VALUE;
+    }
+  }
+
+  ~ScopedEnableVTEscapeProcessing() {
+    if (is_valid())
+      SetConsoleMode(console_, original_mode_);
+  }
+
+  bool is_valid() const { return console_ != INVALID_HANDLE_VALUE; }
+
+  HANDLE console_;
+  DWORD original_mode_;
+};
+#endif
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  base::CommandLine::Init(argc, argv);
+
+#if defined(OS_WIN)
+  ScopedEnableVTEscapeProcessing enable_vt_processing;
+#endif
+  setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+
+  int tests_started = 0;
+
+  const char* test_filter = "*";
+  for (int i = 1; i < argc; ++i) {
+    const char kTestFilterPrefix[] = "--gtest_filter=";
+    if (strncmp(argv[i], kTestFilterPrefix, strlen(kTestFilterPrefix)) == 0) {
+      test_filter = &argv[i][strlen(kTestFilterPrefix)];
+    }
+  }
+
+  int num_active_tests = 0;
+  for (int i = 0; i < ntests; i++) {
+    tests[i].should_run = TestMatchesFilter(tests[i].name, test_filter);
+    if (tests[i].should_run) {
+      ++num_active_tests;
+    }
+  }
+
+  const char* prefix = "";
+  const char* suffix = "\n";
+#if defined(OS_WIN)
+  if (enable_vt_processing.is_valid())
+#else
+  // When run from Xcode, the console returns "true" to isatty(1) but it
+  // does not interprets ANSI escape sequence resulting in difficult to
+  // read output. There is no portable way to detect if the console is
+  // Xcode's console (term is set to xterm or xterm-256colors) but Xcode
+  // sets the __XCODE_BUILT_PRODUCTS_DIR_PATHS environment variable. Use
+  // this as a proxy to detect that the console does not interpret the
+  // ANSI sequences correctly.
+  if (isatty(1) && getenv("__XCODE_BUILT_PRODUCTS_DIR_PATHS") == NULL)
+#endif
+  {
+    prefix = "\r";
+    suffix = "\x1B[K";
+  }
+  bool passed = true;
+  for (int i = 0; i < ntests; i++) {
+    if (!tests[i].should_run)
+      continue;
+
+    ++tests_started;
+    testing::Test* test = tests[i].factory();
+    printf("%s[%d/%d] %s%s", prefix, tests_started, num_active_tests,
+           tests[i].name, suffix);
+    test->SetUp();
+    test->Run();
+    test->TearDown();
+    if (test->Failed())
+      passed = false;
+    delete test;
+  }
+
+  printf("\n%s\n", passed ? "PASSED" : "FAILED");
+  fflush(stdout);
+  return passed ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/util/test/test.h b/src/util/test/test.h
new file mode 100644 (file)
index 0000000..d3fc056
--- /dev/null
@@ -0,0 +1,194 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_TEST_TEST_H_
+#define UTIL_TEST_TEST_H_
+
+#include <string.h>
+
+#include <sstream>
+#include <string>
+
+// This is a minimal googletest-like testing framework. It's originally derived
+// from Ninja's src/test.h. You might prefer that one if you have different
+// tradeoffs (in particular, if you don't need to stream message to assertion
+// failures, Ninja's is a bit simpler.)
+namespace testing {
+
+class Test {
+ public:
+  Test() : failed_(false) {}
+  virtual ~Test() {}
+  virtual void SetUp() {}
+  virtual void TearDown() {}
+  virtual void Run() = 0;
+
+  bool Failed() const { return failed_; }
+
+ private:
+  friend class TestResult;
+
+  bool failed_;
+};
+
+extern testing::Test* g_current_test;
+
+class TestResult {
+ public:
+  TestResult(bool condition, const char* error)
+      : condition_(condition), error_(error) {
+    if (!condition)
+      g_current_test->failed_ = true;
+  }
+
+  operator bool() const { return condition_; }
+  const char* error() const { return error_; }
+
+ private:
+  bool condition_;
+  const char* error_;
+};
+
+class Message {
+ public:
+  Message() {}
+  ~Message() { printf("%s\n\n", ss_.str().c_str()); }
+
+  template <typename T>
+  inline Message& operator<<(const T& val) {
+    ss_ << val;
+    return *this;
+  }
+
+ private:
+  std::stringstream ss_;
+};
+
+class AssertHelper {
+ public:
+  AssertHelper(const char* file, int line, const TestResult& test_result)
+      : file_(file), line_(line), error_(test_result.error()) {}
+
+  void operator=(const Message& message) const {
+    printf("\n*** FAILURE %s:%d: %s\n", file_, line_, error_);
+  }
+
+ private:
+  const char* file_;
+  int line_;
+  const char* error_;
+};
+
+}  // namespace testing
+
+void RegisterTest(testing::Test* (*)(), const char*);
+
+#define TEST_F_(x, y, name)                                                    \
+  struct y : public x {                                                        \
+    static testing::Test* Create() { return testing::g_current_test = new y; } \
+    virtual void Run();                                                        \
+  };                                                                           \
+  struct Register##y {                                                         \
+    Register##y() { RegisterTest(y::Create, name); }                           \
+  };                                                                           \
+  Register##y g_register_##y;                                                  \
+  void y::Run()
+
+#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y)
+#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y)
+
+#define FRIEND_TEST(x, y) friend class x##y
+
+// Some compilers emit a warning if nested "if" statements are followed by an
+// "else" statement and braces are not used to explicitly disambiguate the
+// "else" binding.  This leads to problems with code like:
+//
+//   if (something)
+//     ASSERT_TRUE(condition) << "Some message";
+#define TEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  switch (0)                         \
+  case 0:                            \
+  default:
+
+#define TEST_ASSERT_(expression, on_failure)                  \
+  TEST_AMBIGUOUS_ELSE_BLOCKER_                                \
+  if (const ::testing::TestResult test_result = (expression)) \
+    ;                                                         \
+  else                                                        \
+    on_failure(test_result)
+
+#define TEST_NONFATAL_FAILURE_(message) \
+  ::testing::AssertHelper(__FILE__, __LINE__, message) = ::testing::Message()
+
+#define TEST_FATAL_FAILURE_(message)                            \
+  return ::testing::AssertHelper(__FILE__, __LINE__, message) = \
+             ::testing::Message()
+
+#define EXPECT_EQ(a, b)                                     \
+  TEST_ASSERT_(::testing::TestResult(a == b, #a " == " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_NE(a, b)                                     \
+  TEST_ASSERT_(::testing::TestResult(a != b, #a " != " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_LT(a, b)                                   \
+  TEST_ASSERT_(::testing::TestResult(a < b, #a " < " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_GT(a, b)                                   \
+  TEST_ASSERT_(::testing::TestResult(a > b, #a " > " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_LE(a, b)                                     \
+  TEST_ASSERT_(::testing::TestResult(a <= b, #a " <= " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_GE(a, b)                                     \
+  TEST_ASSERT_(::testing::TestResult(a >= b, #a " >= " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_TRUE(a)                                          \
+  TEST_ASSERT_(::testing::TestResult(static_cast<bool>(a), #a), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_FALSE(a)                                          \
+  TEST_ASSERT_(::testing::TestResult(!static_cast<bool>(a), #a), \
+               TEST_NONFATAL_FAILURE_)
+
+#define EXPECT_STREQ(a, b)                                                \
+  TEST_ASSERT_(::testing::TestResult(strcmp(a, b) == 0, #a " str== " #b), \
+               TEST_NONFATAL_FAILURE_)
+
+#define ASSERT_EQ(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a == b, #a " == " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_NE(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a != b, #a " != " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_LT(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a < b, #a " < " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_GT(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a > b, #a " > " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_LE(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a <= b, #a " <= " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_GE(a, b) \
+  TEST_ASSERT_(::testing::TestResult(a >= b, #a " >= " #b), TEST_FATAL_FAILURE_)
+
+#define ASSERT_TRUE(a)                                          \
+  TEST_ASSERT_(::testing::TestResult(static_cast<bool>(a), #a), \
+               TEST_FATAL_FAILURE_)
+
+#define ASSERT_FALSE(a)                                          \
+  TEST_ASSERT_(::testing::TestResult(!static_cast<bool>(a), #a), \
+               TEST_FATAL_FAILURE_)
+
+#define ASSERT_STREQ(a, b)                                                \
+  TEST_ASSERT_(::testing::TestResult(strcmp(a, b) == 0, #a " str== " #b), \
+               TEST_FATAL_FAILURE_)
+
+#endif  // UTIL_TEST_TEST_H_
diff --git a/src/util/ticks.cc b/src/util/ticks.cc
new file mode 100644 (file)
index 0000000..121c719
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ticks.h"
+
+#include "base/logging.h"
+#include "build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_MACOSX)
+#include <mach/mach_time.h>
+#elif defined(OS_POSIX)
+#include <time.h>
+#else
+#error Port.
+#endif
+
+namespace {
+
+bool g_initialized;
+
+#if defined(OS_WIN)
+LARGE_INTEGER g_frequency;
+LARGE_INTEGER g_start;
+#elif defined(OS_MACOSX)
+mach_timebase_info_data_t g_timebase;
+uint64_t g_start;
+#elif defined(OS_POSIX)
+uint64_t g_start;
+#else
+#error Port.
+#endif
+
+#if !defined(OS_MACOSX)
+constexpr uint64_t kNano = 1'000'000'000;
+#endif
+
+void Init() {
+  DCHECK(!g_initialized);
+
+#if defined(OS_WIN)
+  QueryPerformanceFrequency(&g_frequency);
+  QueryPerformanceCounter(&g_start);
+#elif defined(OS_MACOSX)
+  mach_timebase_info(&g_timebase);
+  g_start = (mach_absolute_time() * g_timebase.numer) / g_timebase.denom;
+#elif defined(OS_POSIX)
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  g_start = static_cast<uint64_t>(ts.tv_sec) * kNano +
+            static_cast<uint64_t>(ts.tv_nsec);
+#else
+#error Port.
+#endif
+
+  g_initialized = true;
+}
+
+}  // namespace
+
+Ticks TicksNow() {
+  static bool initialized = []() {
+    Init();
+    return true;
+  }();
+  DCHECK(initialized);
+
+  Ticks now;
+
+#if defined(OS_WIN)
+  LARGE_INTEGER t;
+  QueryPerformanceCounter(&t);
+  now = ((t.QuadPart - g_start.QuadPart) * kNano) / g_frequency.QuadPart;
+#elif defined(OS_MACOSX)
+  now =
+      ((mach_absolute_time() * g_timebase.numer) / g_timebase.denom) - g_start;
+#elif defined(OS_POSIX)
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  now = (static_cast<uint64_t>(ts.tv_sec) * kNano +
+         static_cast<uint64_t>(ts.tv_nsec)) -
+        g_start;
+#else
+#error Port.
+#endif
+
+  return now;
+}
+
+TickDelta TicksDelta(Ticks new_ticks, Ticks old_ticks) {
+  DCHECK(new_ticks >= old_ticks);
+  return TickDelta(new_ticks - old_ticks);
+}
diff --git a/src/util/ticks.h b/src/util/ticks.h
new file mode 100644 (file)
index 0000000..fcbde05
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_TICKS_H_
+#define UTIL_TICKS_H_
+
+#include <stdint.h>
+
+using Ticks = uint64_t;
+
+class TickDelta {
+ public:
+  explicit TickDelta(uint64_t delta) : delta_(delta) {}
+
+  double InSecondsF() const { return delta_ / 1000000000.0; }
+  double InMillisecondsF() const { return delta_ / 1000000.0; }
+  double InMicrosecondsF() const { return delta_ / 1000.0; }
+  double InNanosecondsF() const { return delta_; }
+
+  uint64_t InSeconds() const { return delta_ / 1000000000; }
+  uint64_t InMilliseconds() const { return delta_ / 1000000; }
+  uint64_t InMicroseconds() const { return delta_ / 1000; }
+  uint64_t InNanoseconds() const { return delta_; }
+
+  uint64_t raw() const { return delta_; }
+
+ private:
+  uint64_t delta_;
+};
+
+Ticks TicksNow();
+
+TickDelta TicksDelta(Ticks new_ticks, Ticks old_ticks);
+
+class ElapsedTimer {
+ public:
+  ElapsedTimer() : start_(TicksNow()) {}
+  TickDelta Elapsed() { return TicksDelta(TicksNow(), start_); }
+
+ private:
+  Ticks start_;
+};
+
+#endif  // UTIL_TICKS_H_
diff --git a/src/util/worker_pool.cc b/src/util/worker_pool.cc
new file mode 100644 (file)
index 0000000..9f6a47b
--- /dev/null
@@ -0,0 +1,150 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/worker_pool.h"
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/switches.h"
+#include "util/build_config.h"
+#include "util/sys_info.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace {
+
+#if defined(OS_WIN)
+class ProcessorGroupSetter {
+ public:
+  void SetProcessorGroup(std::thread* thread);
+
+ private:
+  int group_ = 0;
+  GROUP_AFFINITY group_affinity_;
+  int num_available_cores_in_group_ = ::GetActiveProcessorCount(group_) / 2;
+  const int num_groups_ = ::GetActiveProcessorGroupCount();
+};
+
+void ProcessorGroupSetter::SetProcessorGroup(std::thread* thread) {
+  if (num_groups_ <= 1)
+    return;
+
+  const HANDLE thread_handle = HANDLE(thread->native_handle());
+  ::GetThreadGroupAffinity(thread_handle, &group_affinity_);
+  group_affinity_.Group = group_;
+  const bool success =
+      ::SetThreadGroupAffinity(thread_handle, &group_affinity_, nullptr);
+  DCHECK(success);
+
+  // Move to next group once one thread has been assigned per core in |group_|.
+  num_available_cores_in_group_--;
+  if (num_available_cores_in_group_ <= 0) {
+    group_++;
+    if (group_ >= num_groups_) {
+      group_ = 0;
+    }
+    num_available_cores_in_group_ = ::GetActiveProcessorCount(group_) / 2;
+  }
+}
+#endif
+
+int GetThreadCount() {
+  std::string thread_count =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kThreads);
+
+  // See if an override was specified on the command line.
+  int result;
+  if (!thread_count.empty() && base::StringToInt(thread_count, &result) &&
+      result >= 1) {
+    return result;
+  }
+
+  // Base the default number of worker threads on number of cores in the
+  // system. When building large projects, the speed can be limited by how fast
+  // the main thread can dispatch work and connect the dependency graph. If
+  // there are too many worker threads, the main thread can be starved and it
+  // will run slower overall.
+  //
+  // One less worker thread than the number of physical CPUs seems to be a
+  // good value, both theoretically and experimentally. But always use at
+  // least some workers to prevent us from being too sensitive to I/O latency
+  // on low-end systems.
+  //
+  // The minimum thread count is based on measuring the optimal threads for the
+  // Chrome build on a several-year-old 4-core MacBook.
+  // Almost all CPUs now are hyperthreaded.
+  int num_cores = NumberOfProcessors() / 2;
+  return std::max(num_cores - 1, 8);
+}
+
+}  // namespace
+
+WorkerPool::WorkerPool() : WorkerPool(GetThreadCount()) {}
+
+WorkerPool::WorkerPool(size_t thread_count) : should_stop_processing_(false) {
+#if defined(OS_WIN)
+  ProcessorGroupSetter processor_group_setter;
+#endif
+
+  threads_.reserve(thread_count);
+  for (size_t i = 0; i < thread_count; ++i) {
+    threads_.emplace_back([this]() { Worker(); });
+
+#if defined(OS_WIN)
+    // Set thread processor group. This is needed for systems with more than 64
+    // logical processors, wherein available processors are divided into groups,
+    // and applications that need to use more than one group's processors must
+    // manually assign their threads to groups.
+    processor_group_setter.SetProcessorGroup(&threads_.back());
+#endif
+  }
+}
+
+WorkerPool::~WorkerPool() {
+  {
+    std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+    should_stop_processing_ = true;
+  }
+
+  pool_notifier_.notify_all();
+
+  for (auto& task_thread : threads_) {
+    task_thread.join();
+  }
+}
+
+void WorkerPool::PostTask(std::function<void()> work) {
+  {
+    std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+    CHECK(!should_stop_processing_);
+    task_queue_.emplace(std::move(work));
+  }
+
+  pool_notifier_.notify_one();
+}
+
+void WorkerPool::Worker() {
+  for (;;) {
+    std::function<void()> task;
+
+    {
+      std::unique_lock<std::mutex> queue_lock(queue_mutex_);
+
+      pool_notifier_.wait(queue_lock, [this]() {
+        return (!task_queue_.empty()) || should_stop_processing_;
+      });
+
+      if (should_stop_processing_ && task_queue_.empty())
+        return;
+
+      task = std::move(task_queue_.front());
+      task_queue_.pop();
+    }
+
+    task();
+  }
+}
diff --git a/src/util/worker_pool.h b/src/util/worker_pool.h
new file mode 100644 (file)
index 0000000..7284ebe
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UTIL_WORKER_POOL_H_
+#define UTIL_WORKER_POOL_H_
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+class WorkerPool {
+ public:
+  WorkerPool();
+  WorkerPool(size_t thread_count);
+  ~WorkerPool();
+
+  void PostTask(std::function<void()> work);
+
+ private:
+  void Worker();
+
+  std::vector<std::thread> threads_;
+  std::queue<std::function<void()>> task_queue_;
+  std::mutex queue_mutex_;
+  std::condition_variable_any pool_notifier_;
+  bool should_stop_processing_;
+
+  DISALLOW_COPY_AND_ASSIGN(WorkerPool);
+};
+
+#endif  // UTIL_WORKER_POOL_H_
diff --git a/tools/find_unreachable.py b/tools/find_unreachable.py
new file mode 100755 (executable)
index 0000000..406a312
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+Finds unreachable gn targets by analysing --ide=json output
+from gn gen.
+
+Usage:
+# Generate json file with targets info, will be located at out/project.json:
+gn gen out --ide=json
+# Lists all targets that are not reachable from //:all or //ci:test_all:
+find_unreachable.py --from //:all --from //ci:test_all --json-file out/project.json
+# Lists targets unreachable from //:all that aren't referenced by any other target:
+find_unreachable.py --from //:all --json-file out/project.json --no-refs
+"""
+
+import argparse
+import json
+import sys
+
+
+def find_reachable_targets(known, graph):
+  reachable = set()
+  to_visit = known
+  while to_visit:
+    next = to_visit.pop()
+    if next in reachable:
+      continue
+    reachable.add(next)
+    to_visit += graph[next]['deps']
+  return reachable
+
+
+def find_source_targets_from(targets, graph):
+  source_targets = set(targets)
+  for target in targets:
+    source_targets -= set(graph[target]['deps'])
+  return source_targets
+
+
+def main():
+  parser = argparse.ArgumentParser(description='''
+    Tool to find unreachable targets.
+    This can be useful to inspect forgotten targets,
+    for example tests or intermediate targets in templates
+    that are no longer needed.
+    ''')
+  parser.add_argument(
+    '--json-file', required=True,
+    help='JSON file from gn gen with --ide=json option')
+  parser.add_argument(
+    '--from', action='append', dest='roots',
+    help='Known "root" targets. Can be multiple. Those targets \
+        and all their recursive dependencies are considered reachable.\
+        Examples: //:all, //ci:test_all')
+  parser.add_argument(
+    '--no-refs', action='store_true',
+    help='Show only targets that aren\'t referenced by any other target')
+  cmd_args = parser.parse_args()
+
+  with open(cmd_args.json_file) as json_file:
+    targets_graph = json.load(json_file)['targets']
+
+  reachable = find_reachable_targets(cmd_args.roots, targets_graph)
+  all = set(targets_graph.keys())
+  unreachable = all - reachable
+
+  result = find_source_targets_from(unreachable, targets_graph) \
+    if cmd_args.no_refs else unreachable
+
+  print '\n'.join(sorted(result))
+
+
+if __name__ == '__main__':
+  sys.exit(main())