From 1cd242422a260ae4e65029f23549e186f9cfd0fd Mon Sep 17 00:00:00 2001
From: Marshall Greenblatt <magreenblatt@gmail.com>
Date: Sun, 28 Jul 2024 18:19:47 +0000
Subject: [PATCH] bazel: Add initial config for binary distribution (see #3757)

Add support for building the CEF binary distribution using Bazel
and the default platform toolchain. Tested to work for Windows
x64, MacOS ARM64 and x64 (cross-compile from ARM64), and
Linux x64. Windows x86 (cross-compile from x64) is known to
be broken, see https://github.com/bazelbuild/bazel/issues/22164.

Includes minor changes to tests directory structure to meet
Bazel build requirements.
---
 BUILD.gn                                      |  36 +-
 bazel/BUILD.bazel                             |   8 +
 bazel/copy_filegroups.bzl                     |  61 +++
 bazel/linux/BUILD.bazel                       |   8 +
 bazel/linux/exe_helpers.bzl                   |  58 +++
 bazel/linux/fix_rpath.bzl                     |  41 ++
 bazel/linux/pkg_config/BUILD.bazel            |   7 +
 bazel/linux/pkg_config/BUILD.tmpl             |  32 ++
 bazel/linux/pkg_config/README.cef             |  11 +
 bazel/linux/pkg_config/WORKSPACE              |   2 +
 bazel/linux/pkg_config/pkg_config.bzl         | 194 ++++++++++
 bazel/linux/variables.bzl                     |  68 ++++
 bazel/mac/BUILD.bazel                         |   8 +
 bazel/mac/app_helpers.bzl                     | 108 ++++++
 bazel/mac/variables.bzl                       |  62 +++
 bazel/win/BUILD.bazel                         |   8 +
 bazel/win/cc_env.bzl                          |  33 ++
 bazel/win/exe_helpers.bzl                     |  76 ++++
 bazel/win/mt.bzl                              |  72 ++++
 bazel/win/rc.bzl                              |  50 +++
 bazel/win/setup_sdk.bzl                       | 124 ++++++
 bazel/win/variables.bzl                       | 199 ++++++++++
 bazel/win/wrapper.py.tpl                      |  69 ++++
 cef_paths2.gypi                               |  60 +--
 tests/cefclient/CMakeLists.txt.in             |   9 +-
 .../mac/English.lproj/InfoPlist.strings       |   0
 .../mac/English.lproj/MainMenu.xib            |   0
 .../mac/Info.plist => mac/Info.plist.in}      |   6 +-
 .../{resources => }/mac/cefclient.icns        | Bin
 .../helper-Info.plist.in}                     |   6 +-
 .../win/cefclient.exe.manifest                |   0
 .../{resources => }/win/cefclient.ico         | Bin
 .../{resources => }/win/cefclient.rc          |  54 +--
 tests/cefclient/{resources => }/win/small.ico | Bin
 tests/cefsimple/CMakeLists.txt.in             |   7 +-
 .../mac/{Info.plist => Info.plist.in}         |   4 +-
 ...helper-Info.plist => helper-Info.plist.in} |   4 +
 .../{ => win}/cefsimple.exe.manifest          |   0
 tests/cefsimple/{res => win}/cefsimple.ico    | Bin
 tests/cefsimple/{ => win}/cefsimple.rc        | 158 ++++----
 tests/cefsimple/{res => win}/small.ico        | Bin
 tests/ceftests/CMakeLists.txt.in              |  10 +-
 .../mac/English.lproj/InfoPlist.strings       |   0
 .../mac/English.lproj/MainMenu.xib            |   0
 .../mac/Info.plist => mac/Info.plist.in}      |   4 +-
 .../{resources => }/mac/ceftests.icns         | Bin
 .../helper-Info.plist.in}                     |   4 +
 .../{resources => }/win/ceftests.exe.manifest |   0
 .../ceftests/{resources => }/win/ceftests.ico | Bin
 .../ceftests/{resources => }/win/ceftests.rc  |  10 +-
 tests/ceftests/{resources => }/win/small.ico  | Bin
 tools/bazel_util.py                           | 162 ++++++++
 tools/cef_version.py                          |   9 +-
 tools/distrib/bazel/.bazelrc                  |  54 +++
 tools/distrib/bazel/.bazelversion             |   1 +
 tools/distrib/bazel/BUILD.bazel               | 366 ++++++++++++++++++
 tools/distrib/bazel/MODULE.bazel.in           |  17 +
 tools/distrib/bazel/WORKSPACE                 |  55 +++
 tools/distrib/bazel/bazel-variables.bzl.in    |   8 +
 .../bazel/tests-cefclient-BUILD.bazel.in      | 128 ++++++
 .../bazel/tests-cefclient-linux-BUILD.bazel   |  59 +++
 .../bazel/tests-cefclient-mac-BUILD.bazel     |  53 +++
 .../bazel/tests-cefclient-win-BUILD.bazel     |  59 +++
 .../bazel/tests-cefsimple-BUILD.bazel.in      | 113 ++++++
 .../bazel/tests-cefsimple-linux-BUILD.bazel   |  25 ++
 .../bazel/tests-cefsimple-mac-BUILD.bazel     |  48 +++
 .../bazel/tests-cefsimple-win-BUILD.bazel     |  37 ++
 .../bazel/tests-ceftests-BUILD.bazel.in       | 128 ++++++
 .../bazel/tests-ceftests-linux-BUILD.bazel    |  53 +++
 .../bazel/tests-ceftests-mac-BUILD.bazel      |  53 +++
 .../bazel/tests-ceftests-win-BUILD.bazel      |  59 +++
 tools/distrib/bazel/tests-gtest-BUILD.bazel   |  71 ++++
 .../distrib/bazel/tests-shared-BUILD.bazel.in | 144 +++++++
 tools/distrib/linux/README.minimal.txt        |   7 +
 tools/distrib/linux/README.standard.txt       |  24 ++
 tools/distrib/mac/README.minimal.txt          |   7 +
 tools/distrib/mac/README.redistrib.txt        |   2 +-
 tools/distrib/mac/README.standard.txt         |  26 ++
 tools/distrib/win/README.minimal.txt          |   7 +
 tools/distrib/win/README.standard.txt         |  24 ++
 tools/distrib/win/transfer_standard.cfg       |   6 +-
 tools/make_distrib.py                         | 123 +++++-
 82 files changed, 3404 insertions(+), 195 deletions(-)
 create mode 100644 bazel/BUILD.bazel
 create mode 100644 bazel/copy_filegroups.bzl
 create mode 100755 bazel/linux/BUILD.bazel
 create mode 100755 bazel/linux/exe_helpers.bzl
 create mode 100644 bazel/linux/fix_rpath.bzl
 create mode 100644 bazel/linux/pkg_config/BUILD.bazel
 create mode 100644 bazel/linux/pkg_config/BUILD.tmpl
 create mode 100644 bazel/linux/pkg_config/README.cef
 create mode 100644 bazel/linux/pkg_config/WORKSPACE
 create mode 100644 bazel/linux/pkg_config/pkg_config.bzl
 create mode 100755 bazel/linux/variables.bzl
 create mode 100644 bazel/mac/BUILD.bazel
 create mode 100644 bazel/mac/app_helpers.bzl
 create mode 100644 bazel/mac/variables.bzl
 create mode 100644 bazel/win/BUILD.bazel
 create mode 100644 bazel/win/cc_env.bzl
 create mode 100644 bazel/win/exe_helpers.bzl
 create mode 100644 bazel/win/mt.bzl
 create mode 100644 bazel/win/rc.bzl
 create mode 100644 bazel/win/setup_sdk.bzl
 create mode 100644 bazel/win/variables.bzl
 create mode 100644 bazel/win/wrapper.py.tpl
 rename tests/cefclient/{resources => }/mac/English.lproj/InfoPlist.strings (100%)
 rename tests/cefclient/{resources => }/mac/English.lproj/MainMenu.xib (100%)
 rename tests/cefclient/{resources/mac/Info.plist => mac/Info.plist.in} (93%)
 rename tests/cefclient/{resources => }/mac/cefclient.icns (100%)
 rename tests/cefclient/{resources/mac/helper-Info.plist => mac/helper-Info.plist.in} (91%)
 rename tests/cefclient/{resources => }/win/cefclient.exe.manifest (100%)
 rename tests/cefclient/{resources => }/win/cefclient.ico (100%)
 rename tests/cefclient/{resources => }/win/cefclient.rc (71%)
 rename tests/cefclient/{resources => }/win/small.ico (100%)
 rename tests/cefsimple/mac/{Info.plist => Info.plist.in} (90%)
 rename tests/cefsimple/mac/{helper-Info.plist => helper-Info.plist.in} (88%)
 rename tests/cefsimple/{ => win}/cefsimple.exe.manifest (100%)
 rename tests/cefsimple/{res => win}/cefsimple.ico (100%)
 rename tests/cefsimple/{ => win}/cefsimple.rc (87%)
 rename tests/cefsimple/{res => win}/small.ico (100%)
 rename tests/ceftests/{resources => }/mac/English.lproj/InfoPlist.strings (100%)
 rename tests/ceftests/{resources => }/mac/English.lproj/MainMenu.xib (100%)
 rename tests/ceftests/{resources/mac/Info.plist => mac/Info.plist.in} (90%)
 rename tests/ceftests/{resources => }/mac/ceftests.icns (100%)
 rename tests/ceftests/{resources/mac/helper-Info.plist => mac/helper-Info.plist.in} (88%)
 rename tests/ceftests/{resources => }/win/ceftests.exe.manifest (100%)
 rename tests/ceftests/{resources => }/win/ceftests.ico (100%)
 rename tests/ceftests/{resources => }/win/ceftests.rc (89%)
 rename tests/ceftests/{resources => }/win/small.ico (100%)
 create mode 100644 tools/bazel_util.py
 create mode 100755 tools/distrib/bazel/.bazelrc
 create mode 100644 tools/distrib/bazel/.bazelversion
 create mode 100755 tools/distrib/bazel/BUILD.bazel
 create mode 100644 tools/distrib/bazel/MODULE.bazel.in
 create mode 100755 tools/distrib/bazel/WORKSPACE
 create mode 100644 tools/distrib/bazel/bazel-variables.bzl.in
 create mode 100644 tools/distrib/bazel/tests-cefclient-BUILD.bazel.in
 create mode 100644 tools/distrib/bazel/tests-cefclient-linux-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-cefclient-mac-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-cefclient-win-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-cefsimple-BUILD.bazel.in
 create mode 100644 tools/distrib/bazel/tests-cefsimple-linux-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-cefsimple-mac-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-cefsimple-win-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-ceftests-BUILD.bazel.in
 create mode 100644 tools/distrib/bazel/tests-ceftests-linux-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-ceftests-mac-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-ceftests-win-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-gtest-BUILD.bazel
 create mode 100644 tools/distrib/bazel/tests-shared-BUILD.bazel.in

diff --git a/BUILD.gn b/BUILD.gn
index 06294175c..0d658050a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1968,7 +1968,7 @@ if (is_mac) {
   bundle_data("cefclient_resources_bundle_data") {
     sources = gypi_paths2.shared_sources_resources +
               gypi_paths2.cefclient_sources_resources + [
-      "tests/cefclient/resources/mac/cefclient.icns",
+      "tests/cefclient/mac/cefclient.icns",
     ]
 
     outputs = [
@@ -1978,7 +1978,7 @@ if (is_mac) {
 
   bundle_data("cefclient_resources_bundle_data_english") {
     sources = [
-      "tests/cefclient/resources/mac/English.lproj/InfoPlist.strings",
+      "tests/cefclient/mac/English.lproj/InfoPlist.strings",
     ]
 
     outputs = [
@@ -1988,7 +1988,7 @@ if (is_mac) {
 
   mac_xib_bundle_data("cefclient_xibs") {
     sources = [
-      "tests/cefclient/resources/mac/English.lproj/MainMenu.xib",
+      "tests/cefclient/mac/English.lproj/MainMenu.xib",
     ]
 
     output_path = "{{bundle_resources_dir}}/English.lproj"
@@ -1998,7 +1998,7 @@ if (is_mac) {
     # Necessary because the cef_framework target is testonly.
     testonly = true
 
-    helper_info_plist = "tests/cefclient/resources/mac/helper-Info.plist"
+    helper_info_plist = "tests/cefclient/mac/helper-Info.plist.in"
     helper_sources = includes_common +
                      includes_mac +
                      gypi_paths2.includes_wrapper +
@@ -2012,7 +2012,7 @@ if (is_mac) {
       "CEF_USE_SANDBOX",
     ]
 
-    info_plist = "tests/cefclient/resources/mac/Info.plist"
+    info_plist = "tests/cefclient/mac/Info.plist.in"
     sources = includes_common +
               includes_mac +
               gypi_paths2.includes_wrapper +
@@ -2074,7 +2074,7 @@ if (is_mac) {
     # Necessary because the cef_framework target is testonly.
     testonly = true
 
-    helper_info_plist = "tests/cefsimple/mac/helper-Info.plist"
+    helper_info_plist = "tests/cefsimple/mac/helper-Info.plist.in"
     helper_sources = includes_common +
                      includes_mac +
                      gypi_paths2.includes_wrapper +
@@ -2084,7 +2084,7 @@ if (is_mac) {
       "CEF_USE_SANDBOX",
     ]
 
-    info_plist = "tests/cefsimple/mac/Info.plist"
+    info_plist = "tests/cefsimple/mac/Info.plist.in"
     sources = includes_common +
               includes_mac +
               gypi_paths2.includes_wrapper +
@@ -2111,7 +2111,7 @@ if (is_mac) {
 
   bundle_data("ceftests_resources_bundle_data") {
     sources = gypi_paths2.shared_sources_resources + [
-      "tests/ceftests/resources/mac/ceftests.icns",
+      "tests/ceftests/mac/ceftests.icns",
     ]
 
     outputs = [
@@ -2121,7 +2121,7 @@ if (is_mac) {
 
   bundle_data("ceftests_resources_bundle_data_english") {
     sources = [
-      "tests/ceftests/resources/mac/English.lproj/InfoPlist.strings",
+      "tests/ceftests/mac/English.lproj/InfoPlist.strings",
     ]
 
     outputs = [
@@ -2131,7 +2131,7 @@ if (is_mac) {
 
   mac_xib_bundle_data("ceftests_xibs") {
     sources = [
-      "tests/ceftests/resources/mac/English.lproj/MainMenu.xib",
+      "tests/ceftests/mac/English.lproj/MainMenu.xib",
     ]
     output_path = "{{bundle_resources_dir}}/English.lproj"
   }
@@ -2139,11 +2139,12 @@ if (is_mac) {
   cef_app("ceftests") {
     testonly = true
 
-    helper_info_plist = "tests/ceftests/resources/mac/helper-Info.plist"
+    helper_info_plist = "tests/ceftests/mac/helper-Info.plist.in"
     helper_sources = gypi_paths2.shared_sources_common +
                      gypi_paths2.shared_sources_renderer +
                      gypi_paths2.shared_sources_mac_helper +
-                     gypi_paths2.ceftests_sources_mac_helper
+                     gypi_paths2.ceftests_sources_mac_helper +
+                     gypi_paths2.ceftests_sources_mac_helper_shared
     helper_deps = [
       ":gtest_teamcity",
       "//testing/gtest",
@@ -2156,7 +2157,7 @@ if (is_mac) {
       "CEF_TESTS_IN_SRC_DIRECTORY",
     ]
 
-    info_plist = "tests/ceftests/resources/mac/Info.plist"
+    info_plist = "tests/ceftests/mac/Info.plist.in"
     sources = includes_common +
               includes_mac +
               gypi_paths2.includes_wrapper +
@@ -2241,7 +2242,8 @@ if (is_mac) {
     if (is_win) {
       sources += includes_win +
                  gypi_paths2.shared_sources_win +
-                 gypi_paths2.cefclient_sources_win
+                 gypi_paths2.cefclient_sources_win +
+                 gypi_paths2.cefclient_sources_resources_win_rc
 
       # Set /SUBSYSTEM:WINDOWS.
       configs -= [ "//build/config/win:console" ]
@@ -2337,7 +2339,8 @@ if (is_mac) {
 
     if (is_win) {
       sources += includes_win +
-                 gypi_paths2.cefsimple_sources_win
+                 gypi_paths2.cefsimple_sources_win +
+                 gypi_paths2.cefsimple_sources_resources_win_rc
 
       # Set /SUBSYSTEM:WINDOWS.
       configs -= [ "//build/config/win:console" ]
@@ -2412,7 +2415,8 @@ if (is_mac) {
 
     if (is_win) {
       sources += gypi_paths2.shared_sources_win +
-                 gypi_paths2.ceftests_sources_win
+                 gypi_paths2.ceftests_sources_win +
+                 gypi_paths2.ceftests_sources_resources_win_rc
 
       # Delay-load as many DLLs as possible for sandbox and startup perf
       # improvements.
diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel
new file mode 100644
index 000000000..013cc21d0
--- /dev/null
+++ b/bazel/BUILD.bazel
@@ -0,0 +1,8 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
diff --git a/bazel/copy_filegroups.bzl b/bazel/copy_filegroups.bzl
new file mode 100644
index 000000000..e2ffc56f7
--- /dev/null
+++ b/bazel/copy_filegroups.bzl
@@ -0,0 +1,61 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+def _copy_filegroups_impl(ctx):
+    inputs = ctx.files.filegroups
+    remove_prefixes = ctx.attr.remove_prefixes
+    add_prefix = ctx.attr.add_prefix
+
+    outputs = []
+    for f in inputs:
+        relative_path = f.path
+        for prefix in remove_prefixes:
+            # Add trailing forward slash if necessary.
+            if prefix[-1] != "/":
+                prefix += "/"
+            if len(prefix) > 0 and f.path.startswith(prefix):
+                relative_path = f.path[len(prefix):]
+                break
+
+        if len(add_prefix) > 0:
+            # Add trailing forward slash if necessary.
+            if add_prefix[-1] != "/":
+                add_prefix += "/"
+            relative_path = add_prefix + relative_path
+
+        out = ctx.actions.declare_file(relative_path)
+        outputs.append(out)
+
+        if relative_path.find("/") > 0:
+            command="mkdir -p $(dirname {}) && cp {} {}".format(out.path, f.path, out.path)
+        else:
+            command="cp {} {}".format(f.path, out.path)
+
+        ctx.actions.run_shell(
+            outputs=[out],
+            inputs=depset([f]),
+            command=command
+        )
+
+    # Small sanity check
+    if len(inputs) != len(outputs):
+        fail("Output count should be 1-to-1 with input count.")
+
+    return DefaultInfo(
+        files=depset(outputs),
+        runfiles=ctx.runfiles(files=outputs)
+    )
+
+# Allows the file contents of |filegroups| to be copied next to a cc_binary
+# target via the |data| attribute.
+# Implementation based on https://stackoverflow.com/a/57983629
+copy_filegroups = rule(
+    implementation=_copy_filegroups_impl,
+    attrs={
+        "filegroups": attr.label_list(),
+        "remove_prefixes": attr.string_list(default = []),
+        "add_prefix": attr.string(default = ""),
+    },
+)
+
diff --git a/bazel/linux/BUILD.bazel b/bazel/linux/BUILD.bazel
new file mode 100755
index 000000000..013cc21d0
--- /dev/null
+++ b/bazel/linux/BUILD.bazel
@@ -0,0 +1,8 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
diff --git a/bazel/linux/exe_helpers.bzl b/bazel/linux/exe_helpers.bzl
new file mode 100755
index 000000000..b543e99fe
--- /dev/null
+++ b/bazel/linux/exe_helpers.bzl
@@ -0,0 +1,58 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("//bazel:copy_filegroups.bzl", "copy_filegroups")
+load("//bazel/linux:fix_rpath.bzl", "fix_rpath")
+load("//bazel/linux:variables.bzl",
+     "COMMON_LINKOPTS",
+     "COMMON_COPTS", "COMMON_COPTS_RELEASE", "COMMON_COPTS_DEBUG")
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+
+def declare_exe(name, srcs=[], deps=[], linkopts=[], copts=[], defines=[], data=[]):
+    # Copy SOs and resources into the current project.
+    copy_target = "{}_sos_and_resources".format(name)
+    copy_filegroups(
+        name = copy_target,
+        filegroups = [
+            "//:sos",
+            "//:resources",
+        ],
+        remove_prefixes = [
+            "Debug",
+            "Release",
+            "Resources",
+        ],
+    )
+
+    # Executable target.
+    binary_target = "{}_incorrect_rpath".format(name)
+    cc_binary(
+        name = binary_target,
+        srcs = srcs,
+        deps = [
+            "//:cef_wrapper",
+            "//:cef",
+            "//:cef_sandbox",
+        ] + deps,
+        linkopts = COMMON_LINKOPTS + linkopts,
+        copts = select({
+            "//:linux_dbg": COMMON_COPTS_DEBUG,
+            "//conditions:default": COMMON_COPTS_RELEASE,
+        }) + COMMON_COPTS + copts,
+        defines = defines,
+        data = [
+            ":{}".format(copy_target),
+        ] + data,
+        target_compatible_with = ["@platforms//os:linux"],
+    )
+
+    # Set rpath to $ORIGIN so that libraries can be loaded from next to the
+    # executable.
+    fix_rpath(
+        name = "{}_fixed_rpath".format(name),
+        src = ":{}".format(binary_target),
+        out = name,
+        target_compatible_with = ["@platforms//os:linux"],
+    )
+
diff --git a/bazel/linux/fix_rpath.bzl b/bazel/linux/fix_rpath.bzl
new file mode 100644
index 000000000..3e09f4755
--- /dev/null
+++ b/bazel/linux/fix_rpath.bzl
@@ -0,0 +1,41 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+def _fix_rpath_impl(ctx):
+    inputs = ctx.runfiles(files = [ctx.file.src])
+    # Bring over 'data' dependencies from the input.
+    inputs = inputs.merge_all([ctx.attr.src[DefaultInfo].default_runfiles])
+
+    src = ctx.file.src.path
+    out = ctx.outputs.out.path
+
+    ctx.actions.run_shell(
+        outputs = [ctx.outputs.out],
+        inputs = inputs.files,
+        arguments = [src, out],
+        command = "cp $1 $2 && " +
+                  "chmod +w $2 && " +
+                  "patchelf --remove-rpath $2 && " +
+                  "patchelf --set-rpath '$ORIGIN' $2"
+    )
+
+    return [DefaultInfo(files = depset([ctx.outputs.out]))]
+
+# Set rpath to $ORIGIN so that libraries can be loaded from next to the
+# executable. The result can be confirmed with:
+# $ objdump -x ./bazel-bin/path/to/binary | grep 'R.*PATH'
+#
+# Alternatively, define a custom CC toolchain that overrides
+# 'runtime_library_search_directories'.
+#
+# This rule requires preinstallation of the patchelf package:
+# $ sudo apt install patchelf
+fix_rpath = rule(
+    implementation = _fix_rpath_impl,
+    attrs = {
+        "src": attr.label(allow_single_file = True),
+        "out": attr.output(mandatory = True),
+    },
+)
+
diff --git a/bazel/linux/pkg_config/BUILD.bazel b/bazel/linux/pkg_config/BUILD.bazel
new file mode 100644
index 000000000..3e35deab2
--- /dev/null
+++ b/bazel/linux/pkg_config/BUILD.bazel
@@ -0,0 +1,7 @@
+package(default_visibility = ["//visibility:public"])
+
+exports_files([
+    "pkg_config.bzl",
+    "BUILD.tmpl",
+])
+
diff --git a/bazel/linux/pkg_config/BUILD.tmpl b/bazel/linux/pkg_config/BUILD.tmpl
new file mode 100644
index 000000000..1f6c294f3
--- /dev/null
+++ b/bazel/linux/pkg_config/BUILD.tmpl
@@ -0,0 +1,32 @@
+# vi: ft=bzl
+package(default_visibility = ["//visibility:private"])
+
+_imports = [p[:len(p)-2] for p in glob(["{}/**/*.a".format(d) for d in [%{deps}]])]
+[cc_import(
+    name = i.replace("/", "_"),
+    hdrs = glob([%{hdrs}]),
+    # TODO: library extension for platform.
+    static_library = "{}.a".format(i),
+    shared_library = "{}.dylib".format(i),
+) for i in _imports]
+
+cc_library(
+    name = "internal_lib",
+    hdrs = glob([%{hdrs}]),
+    copts = [%{copts}],
+    includes = [%{includes}],
+    linkopts = [%{linkopts}],
+    deps = [(":" + i.replace("/", "_")) for i in _imports],
+)
+
+cc_library(
+    name = "lib",
+    hdrs = glob(["%{strip_include}/**/*.h"]),
+    copts = [%{extra_copts}],
+    linkopts = [%{extra_linkopts}],
+    deps = [":internal_lib"] + [%{extra_deps}],
+    visibility = ["//visibility:public"],
+    strip_include_prefix = "%{strip_include}",
+    include_prefix = "%{include_prefix}",
+)
+
diff --git a/bazel/linux/pkg_config/README.cef b/bazel/linux/pkg_config/README.cef
new file mode 100644
index 000000000..6da3ba3ac
--- /dev/null
+++ b/bazel/linux/pkg_config/README.cef
@@ -0,0 +1,11 @@
+Name: pkg_config
+URL: https://github.com/cherrry/bazel_pkg_config
+Version: 284219a
+
+Description:
+Bazel rules for pkg-config tools.
+
+CEF-specific changes:
+- Fix failure with duplicate symlinks.
+- Remove `--static` flag from pkg-config invocation.
+
diff --git a/bazel/linux/pkg_config/WORKSPACE b/bazel/linux/pkg_config/WORKSPACE
new file mode 100644
index 000000000..6399ba49b
--- /dev/null
+++ b/bazel/linux/pkg_config/WORKSPACE
@@ -0,0 +1,2 @@
+workspace(name = "pkg_config")
+
diff --git a/bazel/linux/pkg_config/pkg_config.bzl b/bazel/linux/pkg_config/pkg_config.bzl
new file mode 100644
index 000000000..c927d6246
--- /dev/null
+++ b/bazel/linux/pkg_config/pkg_config.bzl
@@ -0,0 +1,194 @@
+def _success(value):
+    return struct(error = None, value = value)
+
+def _error(message):
+    return struct(error = message, value = None)
+
+def _split(result, delimeter = " "):
+    if result.error != None:
+        return result
+    return _success([arg for arg in result.value.strip().split(delimeter) if arg])
+
+def _find_binary(ctx, binary_name):
+    binary = ctx.which(binary_name)
+    if binary == None:
+        return _error("Unable to find binary: {}".format(binary_name))
+    return _success(binary)
+
+def _execute(ctx, binary, args):
+    result = ctx.execute([binary] + args)
+    if result.return_code != 0:
+        return _error("Failed execute {} {}".format(binary, args))
+    return _success(result.stdout)
+
+def _pkg_config(ctx, pkg_config, pkg_name, args):
+    return _execute(ctx, pkg_config, [pkg_name] + args)
+
+def _check(ctx, pkg_config, pkg_name):
+    exist = _pkg_config(ctx, pkg_config, pkg_name, ["--exists"])
+    if exist.error != None:
+        return _error("Package {} does not exist".format(pkg_name))
+
+    if ctx.attr.version != "":
+        version = _pkg_config(ctx, pkg_config, pkg_name, ["--exact-version", ctx.attr.version])
+        if version.error != None:
+            return _error("Require {} version = {}".format(pkg_name, ctx.attr.version))
+
+    if ctx.attr.min_version != "":
+        version = _pkg_config(ctx, pkg_config, pkg_name, ["--atleast-version", ctx.attr.min_version])
+        if version.error != None:
+            return _error("Require {} version >= {}".format(pkg_name, ctx.attr.min_version))
+
+    if ctx.attr.max_version != "":
+        version = _pkg_config(ctx, pkg_config, pkg_name, ["--max-version", ctx.attr.max_version])
+        if version.error != None:
+            return _error("Require {} version <= {}".format(pkg_name, ctx.attr.max_version))
+
+    return _success(None)
+
+def _extract_prefix(flags, prefix, strip = True):
+    stripped, remain = [], []
+    for arg in flags:
+        if arg.startswith(prefix):
+            if strip:
+                stripped += [arg[len(prefix):]]
+            else:
+                stripped += [arg]
+        else:
+            remain += [arg]
+    return stripped, remain
+
+def _includes(ctx, pkg_config, pkg_name):
+    includes = _split(_pkg_config(ctx, pkg_config, pkg_name, ["--cflags-only-I"]))
+    if includes.error != None:
+        return includes
+    includes, unused = _extract_prefix(includes.value, "-I", strip = True)
+    return _success(includes)
+
+def _copts(ctx, pkg_config, pkg_name):
+    return _split(_pkg_config(ctx, pkg_config, pkg_name, [
+        "--cflags-only-other",
+        "--libs-only-L",
+    ]))
+
+def _linkopts(ctx, pkg_config, pkg_name):
+    return _split(_pkg_config(ctx, pkg_config, pkg_name, [
+        "--libs-only-other",
+        "--libs-only-l",
+    ]))
+
+def _ignore_opts(opts, ignore_opts):
+    remain = []
+    for opt in opts:
+        if opt not in ignore_opts:
+            remain += [opt]
+    return remain
+
+def _symlinks(ctx, basename, srcpaths):
+    result = []
+    root = ctx.path("")
+    base = root.get_child(basename)
+    rootlen = len(str(base)) - len(basename)
+    for src in [ctx.path(p) for p in srcpaths]:
+        dest = base.get_child(src.basename)
+        if not dest.exists:
+            ctx.symlink(src, dest)
+        result += [str(dest)[rootlen:]]
+    return result
+
+def _deps(ctx, pkg_config, pkg_name):
+    deps = _split(_pkg_config(ctx, pkg_config, pkg_name, [
+        "--libs-only-L",
+        "--static",
+    ]))
+    if deps.error != None:
+        return deps
+    deps, unused = _extract_prefix(deps.value, "-L", strip = True)
+    result = []
+    for dep in {dep: True for dep in deps}.keys():
+        base = "deps_" + dep.replace("/", "_").replace(".", "_")
+        result += _symlinks(ctx, base, [dep])
+    return _success(result)
+
+def _fmt_array(array):
+    return ",".join(['"{}"'.format(a) for a in array])
+
+def _fmt_glob(array):
+    return _fmt_array(["{}/**/*.h".format(a) for a in array])
+
+def _pkg_config_impl(ctx):
+    pkg_name = ctx.attr.pkg_name
+    if pkg_name == "":
+        pkg_name = ctx.attr.name
+
+    pkg_config = _find_binary(ctx, "pkg-config")
+    if pkg_config.error != None:
+        return pkg_config
+    pkg_config = pkg_config.value
+
+    check = _check(ctx, pkg_config, pkg_name)
+    if check.error != None:
+        return check
+
+    includes = _includes(ctx, pkg_config, pkg_name)
+    if includes.error != None:
+        return includes
+    includes = includes.value
+    includes = _symlinks(ctx, "includes", includes)
+    strip_include = "includes"
+    if len(includes) == 1:
+        strip_include = includes[0]
+    if ctx.attr.strip_include != "":
+        strip_include += "/" + ctx.attr.strip_include
+
+    ignore_opts = ctx.attr.ignore_opts
+    copts = _copts(ctx, pkg_config, pkg_name)
+    if copts.error != None:
+        return copts
+    copts = _ignore_opts(copts.value, ignore_opts)
+
+    linkopts = _linkopts(ctx, pkg_config, pkg_name)
+    if linkopts.error != None:
+        return linkopts
+    linkopts = _ignore_opts(linkopts.value, ignore_opts)
+
+    deps = _deps(ctx, pkg_config, pkg_name)
+    if deps.error != None:
+        return deps
+    deps = deps.value
+
+    include_prefix = ctx.attr.name
+    if ctx.attr.include_prefix != "":
+        include_prefix = ctx.attr.include_prefix + "/" + ctx.attr.name
+
+    build = ctx.template("BUILD", Label("//:BUILD.tmpl"), substitutions = {
+        "%{name}": ctx.attr.name,
+        "%{hdrs}": _fmt_glob(includes),
+        "%{includes}": _fmt_array(includes),
+        "%{copts}": _fmt_array(copts),
+        "%{extra_copts}": _fmt_array(ctx.attr.copts),
+        "%{deps}": _fmt_array(deps),
+        "%{extra_deps}": _fmt_array(ctx.attr.deps),
+        "%{linkopts}": _fmt_array(linkopts),
+        "%{extra_linkopts}": _fmt_array(ctx.attr.linkopts),
+        "%{strip_include}": strip_include,
+        "%{include_prefix}": include_prefix,
+    }, executable = False)
+
+pkg_config = repository_rule(
+    attrs = {
+        "pkg_name": attr.string(doc = "Package name for pkg-config query, default to name."),
+        "include_prefix": attr.string(doc = "Additional prefix when including file, e.g. third_party. Compatible with strip_include option to produce desired include paths."),
+        "strip_include": attr.string(doc = "Strip prefix when including file, e.g. libs, files not included will be invisible. Compatible with include_prefix option to produce desired include paths."),
+        "version": attr.string(doc = "Exact package version."),
+        "min_version": attr.string(doc = "Minimum package version."),
+        "max_version": attr.string(doc = "Maximum package version."),
+        "deps": attr.string_list(doc = "Dependency targets."),
+        "linkopts": attr.string_list(doc = "Extra linkopts value."),
+        "copts": attr.string_list(doc = "Extra copts value."),
+        "ignore_opts": attr.string_list(doc = "Ignore listed opts in copts or linkopts."),
+    },
+    local = True,
+    implementation = _pkg_config_impl,
+)
+
diff --git a/bazel/linux/variables.bzl b/bazel/linux/variables.bzl
new file mode 100755
index 000000000..8b2278d68
--- /dev/null
+++ b/bazel/linux/variables.bzl
@@ -0,0 +1,68 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+#
+# Distribution SOs.
+#
+
+SOS = [
+    "libcef.so",
+    "libEGL.so",
+    "libGLESv2.so",
+    "libvk_swiftshader.so",
+    "libvulkan.so.1",
+]
+
+#
+# Common 'linkopts' for cc_binary targets.
+#
+
+# Standard link libraries.
+STANDARD_LIBS = [
+    "X11",
+]
+
+COMMON_LINKOPTS_DEBUG = [
+]
+
+COMMON_LINKOPTS_RELEASE = [
+]
+
+COMMON_LINKOPTS = [
+    "-l{}".format(lib) for lib in STANDARD_LIBS
+]  + select({
+    "//:linux_dbg": COMMON_LINKOPTS_DEBUG,
+    "//conditions:default": COMMON_LINKOPTS_RELEASE,
+})
+
+#
+# Common 'copts' for cc_libary and cc_binary targets.
+#
+
+COMMON_COPTS = [
+]
+
+COMMON_COPTS_DEBUG = [
+]
+
+COMMON_COPTS_RELEASE = [
+]
+
+#
+# Common 'defines' for cc_libary targets.
+#
+
+COMMON_DEFINES = [
+    # Used by apps to test if the sandbox is enabled
+    "CEF_USE_SANDBOX",
+]
+
+COMMON_DEFINES_DEBUG = [
+]
+
+COMMON_DEFINES_RELEASE = [
+    # Not a debug build
+    "NDEBUG",
+]
+
diff --git a/bazel/mac/BUILD.bazel b/bazel/mac/BUILD.bazel
new file mode 100644
index 000000000..013cc21d0
--- /dev/null
+++ b/bazel/mac/BUILD.bazel
@@ -0,0 +1,8 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
diff --git a/bazel/mac/app_helpers.bzl b/bazel/mac/app_helpers.bzl
new file mode 100644
index 000000000..28e0c5a15
--- /dev/null
+++ b/bazel/mac/app_helpers.bzl
@@ -0,0 +1,108 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
+load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
+load("//bazel:variables.bzl", "VERSION_PLIST")
+load("//bazel/mac:variables.bzl", "MACOS_DEPLOYMENT_TARGET", "MACOS_BUNDLE_ID_BASE",
+                                  "COMMON_LINKOPTS")
+
+def _declare_helper_app(name, info_plist, deps, helper_base_name, helper_suffix):
+    """
+    Creates a Helper .app target.
+    """
+    helper_name = "{} Helper".format(name)
+    bundle_id_suffix = ""
+
+    if helper_suffix:
+        helper_name += " ({})".format(helper_suffix)
+        bundle_id_suffix += ".{}".format(helper_suffix.lower())
+
+    # Helper app bundle Info.plist.
+    expand_template(
+        name = "{}_InfoPList".format(helper_base_name),
+        template = info_plist,
+        out = "{}Info.plist".format(helper_base_name),
+        substitutions = {
+            "${EXECUTABLE_NAME}": helper_name,
+            "${PRODUCT_NAME}": helper_name,
+            "${BUNDLE_ID_SUFFIX}": bundle_id_suffix,
+            "${VERSION_SHORT}": VERSION_PLIST,
+            "${VERSION_LONG}": VERSION_PLIST,
+        },
+    )
+
+    # Helper app bundle.
+    macos_application(
+        name = helper_base_name,
+        bundle_name = helper_name,
+        bundle_id = "{}.{}.helper{}".format(MACOS_BUNDLE_ID_BASE, name.lower(), bundle_id_suffix),
+        infoplists = [":{}_InfoPList".format(helper_base_name)],
+        minimum_os_version = MACOS_DEPLOYMENT_TARGET,
+        deps = [
+            "//:cef_sandbox",
+        ] + deps,
+    )
+
+HELPERS = {
+    "HelperBase": "",
+    "HelperAlerts": "Alerts",
+    "HelperGPU": "GPU",
+    "HelperPlugin": "Plugin",
+    "HelperRenderer": "Renderer",
+}
+
+def declare_all_helper_apps(name, info_plist, deps):
+    """
+    Creates all Helper .app targets.
+    """
+    [_declare_helper_app(
+        name = name,
+        info_plist = info_plist,
+        deps = deps,
+        helper_base_name = h,
+        helper_suffix = v,
+    ) for h, v in HELPERS.items()]
+
+def declare_main_app(name, info_plist, deps, linkopts, resources):
+    """
+    Creates the main .app target.
+    """
+
+    # Main app bundle Info.plist.
+    expand_template(
+        name = "InfoPList",
+        template = info_plist,
+        out = "Info.plist",
+        substitutions = {
+            "${EXECUTABLE_NAME}": name,
+            "${PRODUCT_NAME}": name,
+            "${VERSION_SHORT}": VERSION_PLIST,
+            "${VERSION_LONG}": VERSION_PLIST,
+        },
+    )
+
+    # Main app bindle.
+    macos_application(
+        name = name,
+        additional_contents = {
+            ":HelperBase": "Frameworks",
+            ":HelperAlerts": "Frameworks",
+            ":HelperGPU": "Frameworks",
+            ":HelperPlugin": "Frameworks",
+            ":HelperRenderer": "Frameworks",
+        },
+        bundle_name = name,
+        bundle_id = "{}.{}".format(MACOS_BUNDLE_ID_BASE, name.lower()),
+        infoplists = [":InfoPList"],
+        linkopts = COMMON_LINKOPTS + linkopts,
+        minimum_os_version = MACOS_DEPLOYMENT_TARGET,
+        resources = resources,
+        target_compatible_with = [
+            "@platforms//os:macos",
+        ],
+        deps = [
+            "//:cef_framework",
+        ] + deps,
+    )
diff --git a/bazel/mac/variables.bzl b/bazel/mac/variables.bzl
new file mode 100644
index 000000000..acb9310f1
--- /dev/null
+++ b/bazel/mac/variables.bzl
@@ -0,0 +1,62 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+MACOS_DEPLOYMENT_TARGET="10.15"
+MACOS_BUNDLE_ID_BASE="org.cef"
+CEF_FRAMEWORK_NAME="Chromium Embedded Framework"
+
+#
+# Common 'linkopts' for cc_binary targets.
+#
+
+# Standard link frameworks.
+STANDARD_FRAMEWORKS = [
+    "AppKit",
+]
+
+COMMON_LINKOPTS_DEBUG = [
+]
+
+COMMON_LINKOPTS_RELEASE = [
+]
+
+COMMON_LINKOPTS = [
+    "-framework {}".format(lib) for lib in STANDARD_FRAMEWORKS
+]  + select({
+    "//:macos_dbg": COMMON_LINKOPTS_DEBUG,
+    "//conditions:default": COMMON_LINKOPTS_RELEASE,
+})
+
+#
+# Common 'copts' for cc_libary and cc_binary targets.
+#
+
+COMMON_COPTS = [
+    "-Wno-undefined-var-template",
+    "-Wno-missing-field-initializers",
+    "-Wno-deprecated-copy",
+]
+
+COMMON_COPTS_DEBUG = [
+]
+
+COMMON_COPTS_RELEASE = [
+]
+
+#
+# Common 'defines' for cc_libary targets.
+#
+
+COMMON_DEFINES = [
+    # Used by apps to test if the sandbox is enabled
+    "CEF_USE_SANDBOX",
+]
+
+COMMON_DEFINES_DEBUG = [
+]
+
+COMMON_DEFINES_RELEASE = [
+    # Not a debug build
+    "NDEBUG",
+]
diff --git a/bazel/win/BUILD.bazel b/bazel/win/BUILD.bazel
new file mode 100644
index 000000000..013cc21d0
--- /dev/null
+++ b/bazel/win/BUILD.bazel
@@ -0,0 +1,8 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
diff --git a/bazel/win/cc_env.bzl b/bazel/win/cc_env.bzl
new file mode 100644
index 000000000..482e4b782
--- /dev/null
+++ b/bazel/win/cc_env.bzl
@@ -0,0 +1,33 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", _use_cpp_toolchain="use_cpp_toolchain")
+load("@rules_cc//cc:action_names.bzl", "CPP_COMPILE_ACTION_NAME")
+
+# Since we need windows.h and other headers, we should ensure we have the same
+# development environment as a regular cl.exe call. So use the current toolchain
+# to grab environment variables to feed into the actual rc.exe call
+# Much of this is taken from:
+#   https://github.com/bazelbuild/rules_cc/blob/main/examples/my_c_archive/my_c_archive.bzl
+def collect_compilation_env(ctx):
+    cc_toolchain = find_cpp_toolchain(ctx)
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+
+    compiler_variables = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+    )
+
+    return cc_common.get_environment_variables(
+      feature_configuration = feature_configuration,
+      action_name = CPP_COMPILE_ACTION_NAME,
+      variables = compiler_variables,
+    )
+
+use_cpp_toolchain=_use_cpp_toolchain
diff --git a/bazel/win/exe_helpers.bzl b/bazel/win/exe_helpers.bzl
new file mode 100644
index 000000000..5664fb535
--- /dev/null
+++ b/bazel/win/exe_helpers.bzl
@@ -0,0 +1,76 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("//bazel:copy_filegroups.bzl", "copy_filegroups")
+load("//bazel/win:mt.bzl", "add_manifest")
+load("//bazel/win:rc.bzl", "compile_rc")
+load("//bazel/win:variables.bzl",
+     "COMMON_LINKOPTS",
+     "COMMON_COPTS", "COMMON_COPTS_RELEASE", "COMMON_COPTS_DEBUG")
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+
+def declare_exe(name, srcs, manifest_srcs, rc_file, resources_srcs, resources_deps=[],
+                deps=[], linkopts=[], copts=[], defines=[], data=[]):
+    # Resource file.
+    res_target = "{}_res".format(name)
+    compile_rc(
+        name = res_target,
+        rc_file = rc_file,
+        srcs = resources_srcs,
+        deps = resources_deps,
+        out = "{}.res".format(name),
+        target_compatible_with = ["@platforms//os:windows"],
+    )
+
+    # Copy DLLs and resources into the current project.
+    copy_target = "{}_dlls_and_resources".format(name)
+    copy_filegroups(
+        name = copy_target,
+        filegroups = [
+            "//:dlls",
+            "//:resources",
+        ],
+        remove_prefixes = [
+            "Debug",
+            "Release",
+            "Resources",
+        ],
+    )
+
+    # Executable target.
+    binary_target = "{}_no_manifest".format(name)
+    cc_binary(
+        name = binary_target,
+        srcs = srcs,
+        deps = [
+            "//:cef_wrapper",
+            "//:cef",
+            "//:cef_sandbox",
+        ] + deps,
+        linkopts = [
+            "$(location :{})".format(res_target),
+        ] + COMMON_LINKOPTS + linkopts,
+        copts = select({
+            "//:windows_dbg": COMMON_COPTS_DEBUG,
+            "//conditions:default": COMMON_COPTS_RELEASE,
+        }) + COMMON_COPTS + copts,
+        defines = defines,
+        additional_linker_inputs = [
+            ":{}".format(res_target),
+        ],
+        data = [
+            ":{}".format(copy_target),
+        ] + data,
+        features = ["generate_pdb_file"],
+        target_compatible_with = ["@platforms//os:windows"],
+    )
+
+    # Add manifest and rename to final executable.
+    add_manifest(
+        name = name,
+        mt_files = manifest_srcs,
+        in_binary = ":{}".format(binary_target),
+        out_binary = "{}.exe".format(name),
+        target_compatible_with = ["@platforms//os:windows"],
+    )
diff --git a/bazel/win/mt.bzl b/bazel/win/mt.bzl
new file mode 100644
index 000000000..e28563739
--- /dev/null
+++ b/bazel/win/mt.bzl
@@ -0,0 +1,72 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("//bazel/win:cc_env.bzl", "collect_compilation_env", "use_cpp_toolchain")
+
+# Copy exe and pdb file without tracking the destination as an output.
+# Based on https://github.com/bazelbuild/bazel-skylib/blob/main/rules/private/copy_file_private.bzl
+def _write_copy_cmd(ctx, src, dst):
+    # Most Windows binaries built with MSVC use a certain argument quoting
+    # scheme. Bazel uses that scheme too to quote arguments. However,
+    # cmd.exe uses different semantics, so Bazel's quoting is wrong here.
+    # To fix that we write the command to a .bat file so no command line
+    # quoting or escaping is required.
+    bat = ctx.actions.declare_file(ctx.label.name + "-cmd.bat")
+    src_path = src.path.replace("/", "\\")
+    dst_path = dst.path.replace("/", "\\")
+    ctx.actions.write(
+        output = bat,
+        # Do not use lib/shell.bzl's shell.quote() method, because that uses
+        # Bash quoting syntax, which is different from cmd.exe's syntax.
+        content = "@copy /Y \"%s\" \"%s\" >NUL\n@copy /Y \"%s\" \"%s\" >NUL" % (
+            src_path,
+            dst_path,
+            src_path.replace(".exe", ".pdb"),
+            dst_path.replace(".exe", ".pdb"),
+        ),
+        is_executable = True,
+    )
+    return bat
+
+def _add_mt_impl(ctx):
+    mt_files = ctx.files.mt_files
+    input = ctx.attr.in_binary[DebugPackageInfo].unstripped_file
+    output = ctx.outputs.out_binary
+    bat = _write_copy_cmd(ctx, input, output)
+
+    inputs = mt_files + [input, bat]
+
+    # Bring over 'data' dependencies from the input.
+    deps_inputs =  ctx.runfiles(files = inputs)
+    deps_inputs = deps_inputs.merge_all([ctx.attr.in_binary[DefaultInfo].default_runfiles])
+
+    ctx.actions.run(
+        executable = ctx.executable._tool,
+        inputs = deps_inputs.files,
+        outputs = [output],
+        env = collect_compilation_env(ctx),
+        # The bat file will be executed before the tool command.
+        arguments = [bat.path, "-nologo", "-manifest"] + [f.path for f in mt_files] +
+                    ["-outputresource:{}".format(output.path)],
+        mnemonic = "AddMT"
+    )
+
+    return DefaultInfo(files = depset([output]))
+
+add_manifest = rule(
+    implementation = _add_mt_impl,
+    attrs = {
+        "mt_files": attr.label_list(allow_files = [".manifest"]),
+        "in_binary": attr.label(providers = [CcInfo], allow_single_file = True),
+        "out_binary": attr.output(),
+        "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
+        "_tool": attr.label(
+          default = "@winsdk//:mt_pybin",
+          executable = True,
+          cfg = "exec"
+        )
+    },
+    fragments = ["cpp"],
+    toolchains = use_cpp_toolchain(),
+)
diff --git a/bazel/win/rc.bzl b/bazel/win/rc.bzl
new file mode 100644
index 000000000..b02ad078a
--- /dev/null
+++ b/bazel/win/rc.bzl
@@ -0,0 +1,50 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("//bazel/win:cc_env.bzl", "collect_compilation_env", "use_cpp_toolchain")
+
+def _compile_rc_impl(ctx):
+    rc_file = ctx.file.rc_file
+    output = ctx.outputs.out
+
+    inputs = [rc_file] + ctx.files.srcs
+    includes = ["/i{}/{}".format(ctx.label.package, i) for i in ctx.attr.includes]
+
+    # Grab all include paths/files required for the run
+    for dep in ctx.attr.deps:
+      comp_ctx = dep[CcInfo].compilation_context
+
+      includes += ["/i{}".format(i) for i in comp_ctx.quote_includes.to_list()]
+      includes += ["/i{}".format(i) for i in comp_ctx.system_includes.to_list()]
+      inputs += comp_ctx.headers.to_list()
+
+    ctx.actions.run(
+        executable = ctx.executable._tool,
+        inputs = inputs,
+        outputs = [output],
+        env = collect_compilation_env(ctx),
+        arguments = includes + ["/fo", output.path, rc_file.path],
+        mnemonic = "CompileRC"
+    )
+
+    return DefaultInfo(files = depset([output]))
+
+compile_rc = rule(
+    implementation = _compile_rc_impl,
+    attrs = {
+        "rc_file": attr.label(allow_single_file = [".rc"]),
+        "srcs": attr.label_list(allow_files = True),
+        "deps": attr.label_list(providers = [CcInfo]),
+        "includes": attr.string_list(),
+        "out": attr.output(),
+        "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
+        "_tool": attr.label(
+          default = "@winsdk//:rc_pybin",
+          executable = True,
+          cfg = "exec"
+        )
+    },
+    fragments = ["cpp"],
+    toolchains = use_cpp_toolchain(),
+)
diff --git a/bazel/win/setup_sdk.bzl b/bazel/win/setup_sdk.bzl
new file mode 100644
index 000000000..66b980d0f
--- /dev/null
+++ b/bazel/win/setup_sdk.bzl
@@ -0,0 +1,124 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("@bazel_tools//tools/cpp:windows_cc_configure.bzl", "find_vc_path", "setup_vc_env_vars")
+
+def _get_arch(rctx):
+    if rctx.os.arch == "amd64":
+        return "x64"
+
+def _is_windows(rctx):
+    return rctx.os.name.find("windows") != -1
+
+# Tools in the form <Target>: [<Tool>, <Other files needed for that target>]
+TOOLS = {
+    "mt": {
+        "tool": "mt.exe",
+        "deps": [],
+    },
+    "rc": {
+        "tool": "rc.exe",
+        "deps": ["rcdll.dll"],
+    },
+}
+
+def _setup_tools(rctx, sdk_bin_path, sdk_metadata_path):
+    contents = ""
+
+    rctx.symlink(sdk_metadata_path, "VerUnionMetadata")
+    contents += """
+exports_files(["VerUnionMetadata"])
+"""
+
+    for toolname, toolcfg in TOOLS.items():
+        toolexec = toolcfg["tool"]
+        deps = toolcfg["deps"]
+        direct_deps = [toolexec] + deps
+        shared_deps = toolcfg.get("shared_deps", [])
+
+        # Symlink any tools into the right places
+        for dep in direct_deps:
+            rctx.symlink(
+                "{}/{}".format(sdk_bin_path, dep),
+                dep,
+            )
+
+        # Setting up a filegroup for those dependents
+        contents += """
+filegroup(
+  name = "{}_deps",
+  srcs = {},
+)
+""".format(toolname, direct_deps + shared_deps)
+
+        # Now create a wrapper for this tool that simply calls it
+        rctx.template(
+            "{}_wrapper.py".format(toolname),
+            Label("//bazel/win:wrapper.py.tpl"),
+            substitutions = {
+                "${binary}": toolexec,
+            },
+            executable = True,
+        )
+
+        # And add that newly created wrapper to the BUILD.bazel file
+        contents += """
+py_binary(
+    name = "{0}_pybin",
+    srcs = ["{0}_wrapper.py"],
+    main = "{0}_wrapper.py",
+    data = [
+        "@rules_python//python/runfiles",
+        ":{0}_deps"
+    ],
+    python_version = "PY3",
+)
+""".format(toolname)
+
+    return contents
+
+def _setup_vc_debug_runtime(rctx, sdk_bin_path):
+    ucrtbased_dll = "ucrtbased.dll"
+    rctx.symlink("{}/ucrt/{}".format(sdk_bin_path, ucrtbased_dll), ucrtbased_dll)
+
+    contents = """
+filegroup(
+    name = "vc_debug_runtime",
+    srcs = ["{}"],
+)
+""".format(ucrtbased_dll)
+
+    return contents
+
+def _windows_sdk_impl(rctx):
+    # We only support Windows
+    if not _is_windows(rctx):
+        fail("This rule only supports Windows")
+
+    # Figure out where the SDK is, which is based on a registry key.
+    vc_path = find_vc_path(rctx)
+    env = setup_vc_env_vars(rctx, vc_path, envvars = ["WINDOWSSDKVERBINPATH", "WindowsSdkDir", "WindowsSDKVersion"])
+    sdk_bin_path = "{}{}".format(env["WINDOWSSDKVERBINPATH"], _get_arch(rctx))
+    sdk_metadata_path = "{}UnionMetadata/{}".format(env["WindowsSdkDir"], env["WindowsSDKVersion"])
+
+    # Start with some pre-amble
+    contents = """# Autogenerated by //bazel/win:sdk.bzl
+load("@rules_python//python:defs.bzl", "py_binary")
+
+package(default_visibility = ["//visibility:public"])
+"""
+
+    # Handle setting up tools from our list
+    contents += _setup_tools(rctx, sdk_bin_path, sdk_metadata_path)
+
+    contents += _setup_vc_debug_runtime(rctx, sdk_bin_path)
+
+    rctx.file("BUILD.bazel", contents)
+
+setup_sdk = repository_rule(
+    attrs = {},
+    local = True,
+    configure = True,
+    implementation = _windows_sdk_impl,
+)
diff --git a/bazel/win/variables.bzl b/bazel/win/variables.bzl
new file mode 100644
index 000000000..b2556d571
--- /dev/null
+++ b/bazel/win/variables.bzl
@@ -0,0 +1,199 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+#
+# Distribution DLLs.
+#
+
+# NOTE: libcef.dll is included via the //:cef dependency.
+DLLS = [
+    "chrome_elf.dll",
+    "d3dcompiler_47.dll",
+    "libEGL.dll",
+    "libGLESv2.dll",
+    "vk_swiftshader.dll",
+    "vulkan-1.dll",
+]
+
+DLLS_X64 = [
+    "dxil.dll",
+    "dxcompiler.dll",
+]
+
+#
+# Common 'linkopts' for cc_binary targets.
+#
+
+# Windows delayload DLLs.
+# Delayload most libraries as the DLLs are simply not required at startup (or
+# at all, depending on the process type). Some dlls open handles when they are
+# loaded, and we may not want them to be loaded in renderers or other sandboxed
+# processes. Conversely, some DLLs must be loaded before sandbox lockdown. In
+# unsandboxed processes they will load when first needed. The linker will
+# automatically ignore anything which is not linked to the binary at all (it is
+# harmless to have an unmatched /delayload). This list should be kept in sync
+# with Chromium's "delayloads" target from the //build/config/win/BUILD.gn file.
+DELAYLOAD_DLLS = [
+    "api-ms-win-core-winrt-error-l1-1-0.dll",
+    "api-ms-win-core-winrt-l1-1-0.dll",
+    "api-ms-win-core-winrt-string-l1-1-0.dll",
+    "advapi32.dll",
+    "comctl32.dll",
+    "comdlg32.dll",
+    "credui.dll",
+    "cryptui.dll",
+    "d3d11.dll",
+    "d3d9.dll",
+    "dwmapi.dll",
+    "dxgi.dll",
+    "dxva2.dll",
+    "esent.dll",
+    "gdi32.dll",
+    "hid.dll",
+    "imagehlp.dll",
+    "imm32.dll",
+    "msi.dll",
+    "netapi32.dll",
+    "ncrypt.dll",
+    "ole32.dll",
+    "oleacc.dll",
+    "propsys.dll",
+    "psapi.dll",
+    "rpcrt4.dll",
+    "rstrtmgr.dll",
+    "setupapi.dll",
+    "shell32.dll",
+    "shlwapi.dll",
+    "uiautomationcore.dll",
+    "urlmon.dll",
+    "user32.dll",
+    "usp10.dll",
+    "uxtheme.dll",
+    "wer.dll",
+    "wevtapi.dll",
+    "wininet.dll",
+    "winusb.dll",
+    "wsock32.dll",
+    "wtsapi32.dll",
+]
+
+# Standard link libraries.
+STANDARD_LIBS = [
+    "comctl32.lib",
+    "gdi32.lib",
+    "rpcrt4.lib",
+    "shlwapi.lib",
+    "user32.lib",
+    "ws2_32.lib",
+]
+
+# Sandbox link libraries.
+SANDBOX_LIBS = [
+    "Advapi32.lib",
+    "dbghelp.lib",
+    "Delayimp.lib",
+    "ntdll.lib",
+    "OleAut32.lib",
+    "PowrProf.lib",
+    "Propsys.lib",
+    "psapi.lib",
+    "SetupAPI.lib",
+    "Shcore.lib",
+    "Shell32.lib",
+    "Userenv.lib",
+    "version.lib",
+    "wbemuuid.lib",
+    "WindowsApp.lib",
+    "winmm.lib",
+]
+
+COMMON_LINKOPTS_DEBUG = [
+]
+
+COMMON_LINKOPTS_RELEASE = [
+]
+
+COMMON_LINKOPTS = [
+    # No default manifest (see compile_rc target).
+    "/MANIFEST:NO",
+    # Allow 32-bit processes to access 3GB of RAM.
+    "/LARGEADDRESSAWARE",
+    # Generate Debug information.
+    # TODO: Remove after fixing opt builds to work without it.
+    "/DEBUG",
+] + [
+    "/DELAYLOAD:{}".format(dll) for dll in DELAYLOAD_DLLS
+] + [
+    "/DEFAULTLIB:{}".format(lib) for lib in STANDARD_LIBS
+] + select({
+    # Set the initial stack size to 0.5MiB, instead of the 1.5MiB minimum
+    # needed by CEF's main thread. This saves significant memory on threads
+    # (like those in the Windows thread pool, and others) whose stack size we
+    # can only control through this setting. The main thread (in 32-bit builds
+    # only) uses fibers to switch to a 4MiB stack at runtime via
+    # CefRunWinMainWithPreferredStackSize().
+    "//:windows_32": ["/STACK:0x80000"],
+    # Increase the initial stack size to 8MiB from the default 1MiB.
+    "//conditions:default": ["/STACK:0x800000"],
+}) + select({
+    "//:windows_dbg": COMMON_LINKOPTS_DEBUG,
+    "//conditions:default": COMMON_LINKOPTS_RELEASE,
+})
+
+#
+# Common 'copts' for cc_libary and cc_binary targets.
+#
+
+COMMON_COPTS = [
+]
+
+COMMON_COPTS_DEBUG = [
+]
+
+COMMON_COPTS_RELEASE = [
+]
+
+#
+# Common 'defines' for cc_libary targets.
+#
+
+COMMON_DEFINES = [
+    # Windows platform
+    "WIN32",
+    "_WIN32",
+    "_WINDOWS",
+    # Unicode build           
+    "UNICODE",
+    "_UNICODE",
+    # Targeting Windows 10. We can't say `=_WIN32_WINNT_WIN10` here because
+    # some files do `#if WINVER < 0x0600` without including windows.h before,
+    # and then _WIN32_WINNT_WIN10 isn't yet known to be 0x0A00.
+    "WINVER=0x0A00",
+    "_WIN32_WINNT=0x0A00",
+    "NTDDI_VERSION=NTDDI_WIN10_FE",
+    # Use the standard's templated min/max
+    "NOMINMAX",
+    # Exclude less common API declarations
+    "WIN32_LEAN_AND_MEAN",
+    # Disable exceptions
+    "_HAS_EXCEPTIONS=0",
+
+    # Required by cef_sandbox.lib
+    "PSAPI_VERSION=1",
+    # Used by apps to test if the sandbox is enabled
+    "CEF_USE_SANDBOX",
+]
+
+COMMON_DEFINES_DEBUG = [
+    # Required by cef_sandbox.lib
+    # Disable iterator debugging
+    "HAS_ITERATOR_DEBUGGING=0",
+    "_ITERATOR_DEBUG_LEVEL=0",
+]
+
+COMMON_DEFINES_RELEASE = [
+    # Not a debug build
+    "NDEBUG",
+    "_NDEBUG",
+]
diff --git a/bazel/win/wrapper.py.tpl b/bazel/win/wrapper.py.tpl
new file mode 100644
index 000000000..baf278675
--- /dev/null
+++ b/bazel/win/wrapper.py.tpl
@@ -0,0 +1,69 @@
+# Copyright (c) 2024 The Chromium Embedded Framework 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 subprocess
+import sys
+from rules_python.python.runfiles import runfiles
+
+REPLACEMENTS = {
+  "XXX_GETCWD_XXX": os.getcwd(),
+}
+
+def replace_in_str(input):
+  output = input
+  for placeholder, replacement in REPLACEMENTS.items():
+    if placeholder in output:
+      output = output.replace(placeholder, replacement)
+  return output
+
+def print_error(str):
+  print(str, file=sys.stderr)
+
+r = runfiles.Create()
+wrapped_binary = r.Rlocation("winsdk/${binary}")
+args = list(map(replace_in_str, sys.argv[1:]))
+
+# Optionally execute a script before tool execution.
+if args[0].endswith('.bat') or args[0].endswith('.cmd'):
+  if sys.platform != 'win32':
+    raise RuntimeError("Error running bat file; unsupported platform")
+
+  # Execute the .bat file first.
+  bat_file = args[0].replace('/', '\\')
+  p = subprocess.run(
+      bat_file, shell=True,
+      stdout = subprocess.PIPE,
+      stderr = subprocess.STDOUT,
+      text=True)
+  if p.returncode != 0:
+    print_error("[Generated python wrapper] Error running bat file:")
+    print_error(f"CWD: {os.getcwd()}")
+    print_error(f"EXEC: {bat_file}")
+    print_error(f"Exec output:")
+    print_error(p.stdout)
+    raise RuntimeError(f"Error running bat file; {bat_file}")
+
+  args = args[1:]
+
+try:
+  p = subprocess.run(
+      [wrapped_binary] + args,
+      stdout = subprocess.PIPE,
+      stderr = subprocess.STDOUT,
+      text=True)
+  if p.returncode != 0:
+    print_error("[Generated python wrapper] Error running command:")
+    print_error(f"CWD: {os.getcwd()}")
+    print_error(f"EXEC: {wrapped_binary}")
+    print_error(f"ARGS: {args}")
+    print_error(f"Exec output:")
+    print_error(p.stdout)
+    raise RuntimeError(f"Error running wrapped command; {wrapped_binary}")
+except OSError as e:
+  print_error("[Generated python wrapper] Error running command:")
+  print_error(f"CWD: {os.getcwd()}")
+  print_error(f"EXEC: {wrapped_binary}")
+  print_error(f"ARGS: {args}")
+  raise
diff --git a/cef_paths2.gypi b/cef_paths2.gypi
index a6704885b..70c6d93ca 100644
--- a/cef_paths2.gypi
+++ b/cef_paths2.gypi
@@ -370,12 +370,14 @@
       'tests/cefclient/browser/window_test_runner_win.cc',
       'tests/cefclient/browser/window_test_runner_win.h',
       'tests/cefclient/cefclient_win.cc',
-      'tests/cefclient/resources/win/cefclient.rc',
     ],
     'cefclient_sources_resources_win': [
-      'tests/cefclient/resources/win/cefclient.exe.manifest',
-      'tests/cefclient/resources/win/cefclient.ico',
-      'tests/cefclient/resources/win/small.ico',
+      'tests/cefclient/win/cefclient.exe.manifest',
+      'tests/cefclient/win/cefclient.ico',
+      'tests/cefclient/win/small.ico',
+    ],
+    'cefclient_sources_resources_win_rc': [
+      'tests/cefclient/win/cefclient.rc',
     ],
     'cefclient_sources_mac': [
       'tests/cefclient/browser/browser_window_osr_mac.h',
@@ -398,12 +400,12 @@
       'tests/cefclient/browser/window_test_runner_mac.h',
       'tests/cefclient/browser/window_test_runner_mac.mm',
       'tests/cefclient/cefclient_mac.mm',
-   ],
+    ],
     'cefclient_bundle_resources_mac': [
-      'tests/cefclient/resources/mac/cefclient.icns',
-      'tests/cefclient/resources/mac/English.lproj/InfoPlist.strings',
-      'tests/cefclient/resources/mac/English.lproj/MainMenu.xib',
-      'tests/cefclient/resources/mac/Info.plist',
+      'tests/cefclient/mac/cefclient.icns',
+      'tests/cefclient/mac/English.lproj/InfoPlist.strings',
+      'tests/cefclient/mac/English.lproj/MainMenu.xib',
+      'tests/cefclient/mac/Info.plist.in',
     ],
     'cefclient_sources_linux': [
       'tests/cefclient/browser/browser_window_osr_gtk.cc',
@@ -435,15 +437,17 @@
       'tests/cefsimple/simple_handler.h',
     ],
     'cefsimple_sources_win': [
-      'tests/cefsimple/cefsimple.rc',
       'tests/cefsimple/cefsimple_win.cc',
-      'tests/cefsimple/simple_handler_win.cc',
       'tests/cefsimple/resource.h',
+      'tests/cefsimple/simple_handler_win.cc',
     ],
     'cefsimple_sources_resources_win': [
-      'tests/cefsimple/cefsimple.exe.manifest',
-      'tests/cefsimple/res/cefsimple.ico',
-      'tests/cefsimple/res/small.ico',
+      'tests/cefsimple/win/cefsimple.exe.manifest',
+      'tests/cefsimple/win/cefsimple.ico',
+      'tests/cefsimple/win/small.ico',
+    ],
+    'cefsimple_sources_resources_win_rc': [
+      'tests/cefsimple/win/cefsimple.rc',
     ],
     'cefsimple_sources_mac': [
       'tests/cefsimple/cefsimple_mac.mm',
@@ -456,7 +460,7 @@
       'tests/cefsimple/mac/cefsimple.icns',
       'tests/cefsimple/mac/English.lproj/InfoPlist.strings',
       'tests/cefsimple/mac/English.lproj/MainMenu.xib',
-      'tests/cefsimple/mac/Info.plist',
+      'tests/cefsimple/mac/Info.plist.in',
     ],
     'cefsimple_sources_linux': [
       'tests/cefsimple/cefsimple_linux.cc',
@@ -574,24 +578,32 @@
     'ceftests_sources_win': [
       'tests/ceftests/resource_util_win_dir.cc',
       'tests/ceftests/resource_util_win_idmap.cc',
-      'tests/ceftests/resources/win/ceftests.rc',
     ],
     'ceftests_sources_resources_win': [
-      'tests/ceftests/resources/win/ceftests.exe.manifest',
-      'tests/ceftests/resources/win/ceftests.ico',
-      'tests/ceftests/resources/win/small.ico',
+      'tests/ceftests/win/ceftests.exe.manifest',
+      'tests/ceftests/win/ceftests.ico',
+      'tests/ceftests/win/small.ico',
+    ],
+    'ceftests_sources_resources_win_rc': [
+      'tests/ceftests/win/ceftests.rc',
     ],
     'ceftests_sources_mac': [
       'tests/ceftests/os_rendering_unittest_mac.h',
       'tests/ceftests/os_rendering_unittest_mac.mm',
       'tests/ceftests/run_all_unittests_mac.mm',
     ],
-    'ceftests_sources_mac_helper': [
+    'ceftests_sources_mac_browser_shared': [
+      'tests/shared/renderer/client_app_renderer.h',
+    ],
+    'ceftests_sources_mac_helper_shared': [
+      'tests/shared/browser/client_app_browser.h',
       'tests/shared/browser/file_util.cc',
       'tests/shared/browser/file_util.h',
       'tests/shared/browser/resource_util.h',
       'tests/shared/browser/resource_util_mac.mm',
       'tests/shared/browser/resource_util_posix.cc',
+    ],
+    'ceftests_sources_mac_helper': [
       'tests/ceftests/audio_output_unittest.cc',
       'tests/ceftests/client_app_delegates.cc',
       'tests/ceftests/cookie_unittest.cc',
@@ -647,10 +659,10 @@
       'tests/ceftests/v8_unittest.cc',
     ],
     'ceftests_bundle_resources_mac': [
-      'tests/ceftests/resources/mac/ceftests.icns',
-      'tests/ceftests/resources/mac/English.lproj/InfoPlist.strings',
-      'tests/ceftests/resources/mac/English.lproj/MainMenu.xib',
-      'tests/ceftests/resources/mac/Info.plist',
+      'tests/ceftests/mac/ceftests.icns',
+      'tests/ceftests/mac/English.lproj/InfoPlist.strings',
+      'tests/ceftests/mac/English.lproj/MainMenu.xib',
+      'tests/ceftests/mac/Info.plist.in',
     ],
     'ceftests_sources_linux': [
       'tests/ceftests/resource_util_linux.cc',
diff --git a/tests/cefclient/CMakeLists.txt.in b/tests/cefclient/CMakeLists.txt.in
index 060da49ed..45c1d688a 100644
--- a/tests/cefclient/CMakeLists.txt.in
+++ b/tests/cefclient/CMakeLists.txt.in
@@ -72,6 +72,7 @@
   'includes': [
     'shared_sources_win',
     'cefclient_sources_win',
+    'cefclient_sources_resources_win_rc',
   ],
 }}
 
@@ -199,7 +200,7 @@ if(OS_MAC)
   add_dependencies(${CEF_TARGET} libcef_dll_wrapper)
   target_link_libraries(${CEF_TARGET} libcef_dll_wrapper ${CEF_STANDARD_LIBS} "-framework OpenGL")
   set_target_properties(${CEF_TARGET} PROPERTIES
-    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/Info.plist
+    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist.in
     )
 
   # Copy the CEF framework into the Frameworks directory.
@@ -229,7 +230,7 @@ if(OS_MAC)
     # MACOSX_BUNDLE_INFO_PLIST) uses global env variables and would insert the
     # wrong values with multiple targets.
     set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist")
-    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/helper-Info.plist" _plist_contents)
+    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/mac/helper-Info.plist.in" _plist_contents)
     string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents})
@@ -268,7 +269,7 @@ if(OS_MAC)
   # directive but that doesn't properly handle nested resource directories.
   # Remove these prefixes from input file paths.
   set(PREFIXES
-    "resources/mac/"
+    "mac/"
     "resources/"
     "../shared/resources/"
     )
@@ -311,7 +312,7 @@ if(OS_WINDOWS)
   endif()
 
   # Add the custom manifest files to the executable.
-  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}/resources/win" "${CEF_TARGET}" "exe")
+  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}/win" "${CEF_TARGET}" "exe")
 
   # Copy CEF binary and resource files to the target output directory.
   COPY_FILES("${CEF_TARGET}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}")
diff --git a/tests/cefclient/resources/mac/English.lproj/InfoPlist.strings b/tests/cefclient/mac/English.lproj/InfoPlist.strings
similarity index 100%
rename from tests/cefclient/resources/mac/English.lproj/InfoPlist.strings
rename to tests/cefclient/mac/English.lproj/InfoPlist.strings
diff --git a/tests/cefclient/resources/mac/English.lproj/MainMenu.xib b/tests/cefclient/mac/English.lproj/MainMenu.xib
similarity index 100%
rename from tests/cefclient/resources/mac/English.lproj/MainMenu.xib
rename to tests/cefclient/mac/English.lproj/MainMenu.xib
diff --git a/tests/cefclient/resources/mac/Info.plist b/tests/cefclient/mac/Info.plist.in
similarity index 93%
rename from tests/cefclient/resources/mac/Info.plist
rename to tests/cefclient/mac/Info.plist.in
index 468a0284b..66896d110 100644
--- a/tests/cefclient/resources/mac/Info.plist
+++ b/tests/cefclient/mac/Info.plist.in
@@ -16,12 +16,12 @@
 	<string>${PRODUCT_NAME}</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.0</string>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/cefclient/resources/mac/cefclient.icns b/tests/cefclient/mac/cefclient.icns
similarity index 100%
rename from tests/cefclient/resources/mac/cefclient.icns
rename to tests/cefclient/mac/cefclient.icns
diff --git a/tests/cefclient/resources/mac/helper-Info.plist b/tests/cefclient/mac/helper-Info.plist.in
similarity index 91%
rename from tests/cefclient/resources/mac/helper-Info.plist
rename to tests/cefclient/mac/helper-Info.plist.in
index 5f452f39f..701ea8049 100644
--- a/tests/cefclient/resources/mac/helper-Info.plist
+++ b/tests/cefclient/mac/helper-Info.plist.in
@@ -16,10 +16,12 @@
 	<string>${PRODUCT_NAME}</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/cefclient/resources/win/cefclient.exe.manifest b/tests/cefclient/win/cefclient.exe.manifest
similarity index 100%
rename from tests/cefclient/resources/win/cefclient.exe.manifest
rename to tests/cefclient/win/cefclient.exe.manifest
diff --git a/tests/cefclient/resources/win/cefclient.ico b/tests/cefclient/win/cefclient.ico
similarity index 100%
rename from tests/cefclient/resources/win/cefclient.ico
rename to tests/cefclient/win/cefclient.ico
diff --git a/tests/cefclient/resources/win/cefclient.rc b/tests/cefclient/win/cefclient.rc
similarity index 71%
rename from tests/cefclient/resources/win/cefclient.rc
rename to tests/cefclient/win/cefclient.rc
index 756dd4da3..7c00dbc72 100644
--- a/tests/cefclient/resources/win/cefclient.rc
+++ b/tests/cefclient/win/cefclient.rc
@@ -29,33 +29,33 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 // Binary
 //
 
-IDS_BINARY_TRANSFER_HTML BINARY "..\\binary_transfer.html"
-IDS_BINDING_HTML BINARY "..\\binding.html"
-IDS_DIALOGS_HTML BINARY "..\\dialogs.html"
-IDS_DRAGGABLE_HTML BINARY "..\\draggable.html"
-IDS_HANG_HTML BINARY "..\\hang.html"
-IDS_IPC_PERFORMANCE_HTML BINARY "..\\ipc_performance.html"
-IDS_LOCALSTORAGE_HTML BINARY "..\\localstorage.html"
-IDS_LOGO_PNG BINARY "..\\logo.png"
-IDS_MEDIA_ROUTER_HTML BINARY "..\\media_router.html"
-IDS_MENU_ICON_1X_PNG BINARY "..\\menu_icon.1x.png"
-IDS_MENU_ICON_2X_PNG BINARY "..\\menu_icon.2x.png"
-IDS_OSRTEST_HTML BINARY "..\\..\\..\\shared\\resources\\osr_test.html"
-IDS_OTHER_TESTS_HTML BINARY "..\\other_tests.html"
-IDS_PDF_HTML BINARY "..\\..\\..\\shared\\resources\\pdf.html"
-IDS_PDF_PDF BINARY "..\\..\\..\\shared\\resources\\pdf.pdf"
-IDS_PERFORMANCE_HTML BINARY "..\\performance.html"
-IDS_PERFORMANCE2_HTML BINARY "..\\performance2.html"
-IDS_PREFERENCES_HTML BINARY "..\\preferences.html"
-IDS_RESPONSE_FILTER_HTML BINARY "..\\response_filter.html"
-IDS_SERVER_HTML BINARY "..\\server.html"
-IDS_TRANSPARENCY_HTML BINARY "..\\transparency.html"
-IDS_URLREQUEST_HTML BINARY "..\\urlrequest.html"
-IDS_WEBSOCKET_HTML BINARY "..\\websocket.html"
-IDS_WINDOW_HTML BINARY "..\\window.html"
-IDS_WINDOW_ICON_1X_PNG BINARY "..\\..\\..\\shared\\resources\\window_icon.1x.png"
-IDS_WINDOW_ICON_2X_PNG BINARY "..\\..\\..\\shared\\resources\\window_icon.2x.png"
-IDS_XMLHTTPREQUEST_HTML BINARY "..\\xmlhttprequest.html"
+IDS_BINARY_TRANSFER_HTML BINARY "tests\\cefclient\\resources\\binary_transfer.html"
+IDS_BINDING_HTML BINARY "tests\\cefclient\\resources\\binding.html"
+IDS_DIALOGS_HTML BINARY "tests\\cefclient\\resources\\dialogs.html"
+IDS_DRAGGABLE_HTML BINARY "tests\\cefclient\\resources\\draggable.html"
+IDS_HANG_HTML BINARY "tests\\cefclient\\resources\\hang.html"
+IDS_IPC_PERFORMANCE_HTML BINARY "tests\\cefclient\\resources\\ipc_performance.html"
+IDS_LOCALSTORAGE_HTML BINARY "tests\\cefclient\\resources\\localstorage.html"
+IDS_LOGO_PNG BINARY "tests\\cefclient\\resources\\logo.png"
+IDS_MEDIA_ROUTER_HTML BINARY "tests\\cefclient\\resources\\media_router.html"
+IDS_MENU_ICON_1X_PNG BINARY "tests\\cefclient\\resources\\menu_icon.1x.png"
+IDS_MENU_ICON_2X_PNG BINARY "tests\\cefclient\\resources\\menu_icon.2x.png"
+IDS_OSRTEST_HTML BINARY "tests\\shared\\resources\\osr_test.html"
+IDS_OTHER_TESTS_HTML BINARY "tests\\cefclient\\resources\\other_tests.html"
+IDS_PDF_HTML BINARY "tests\\shared\\resources\\pdf.html"
+IDS_PDF_PDF BINARY "tests\\shared\\resources\\pdf.pdf"
+IDS_PERFORMANCE_HTML BINARY "tests\\cefclient\\resources\\performance.html"
+IDS_PERFORMANCE2_HTML BINARY "tests\\cefclient\\resources\\performance2.html"
+IDS_PREFERENCES_HTML BINARY "tests\\cefclient\\resources\\preferences.html"
+IDS_RESPONSE_FILTER_HTML BINARY "tests\\cefclient\\resources\\response_filter.html"
+IDS_SERVER_HTML BINARY "tests\\cefclient\\resources\\server.html"
+IDS_TRANSPARENCY_HTML BINARY "tests\\cefclient\\resources\\transparency.html"
+IDS_URLREQUEST_HTML BINARY "tests\\cefclient\\resources\\urlrequest.html"
+IDS_WEBSOCKET_HTML BINARY "tests\\cefclient\\resources\\websocket.html"
+IDS_WINDOW_HTML BINARY "tests\\cefclient\\resources\\window.html"
+IDS_WINDOW_ICON_1X_PNG BINARY "tests\\shared\\resources\\window_icon.1x.png"
+IDS_WINDOW_ICON_2X_PNG BINARY "tests\\shared\\resources\\window_icon.2x.png"
+IDS_XMLHTTPREQUEST_HTML BINARY "tests\\cefclient\\resources\\xmlhttprequest.html"
 
 /////////////////////////////////////////////////////////////////////////////
 //
diff --git a/tests/cefclient/resources/win/small.ico b/tests/cefclient/win/small.ico
similarity index 100%
rename from tests/cefclient/resources/win/small.ico
rename to tests/cefclient/win/small.ico
diff --git a/tests/cefsimple/CMakeLists.txt.in b/tests/cefsimple/CMakeLists.txt.in
index 9eb0824ac..013b62b8d 100644
--- a/tests/cefsimple/CMakeLists.txt.in
+++ b/tests/cefsimple/CMakeLists.txt.in
@@ -13,6 +13,7 @@
   'includes': [
     'cefsimple_sources_common',
     'cefsimple_sources_win:WINDOWS',
+    'cefsimple_sources_resources_win_rc:WINDOWS',
     'cefsimple_sources_mac:MAC',
     'cefsimple_sources_linux:LINUX',
   ],
@@ -115,7 +116,7 @@ if(OS_MAC)
   add_dependencies(${CEF_TARGET} libcef_dll_wrapper)
   target_link_libraries(${CEF_TARGET} libcef_dll_wrapper ${CEF_STANDARD_LIBS})
   set_target_properties(${CEF_TARGET} PROPERTIES
-    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist
+    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist.in
     )
 
   # Copy the CEF framework into the Frameworks directory.
@@ -145,7 +146,7 @@ if(OS_MAC)
     # MACOSX_BUNDLE_INFO_PLIST) uses global env variables and would insert the
     # wrong values with multiple targets.
     set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist")
-    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/mac/helper-Info.plist" _plist_contents)
+    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/mac/helper-Info.plist.in" _plist_contents)
     string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents})
@@ -206,7 +207,7 @@ if(OS_WINDOWS)
   endif()
 
   # Add the custom manifest files to the executable.
-  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}" "${CEF_TARGET}" "exe")
+  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}/win" "${CEF_TARGET}" "exe")
 
   # Copy binary and resource files to the target output directory.
   COPY_FILES("${CEF_TARGET}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}")
diff --git a/tests/cefsimple/mac/Info.plist b/tests/cefsimple/mac/Info.plist.in
similarity index 90%
rename from tests/cefsimple/mac/Info.plist
rename to tests/cefsimple/mac/Info.plist.in
index 8e1a30622..c8908f34f 100644
--- a/tests/cefsimple/mac/Info.plist
+++ b/tests/cefsimple/mac/Info.plist.in
@@ -19,7 +19,9 @@
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.0</string>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/cefsimple/mac/helper-Info.plist b/tests/cefsimple/mac/helper-Info.plist.in
similarity index 88%
rename from tests/cefsimple/mac/helper-Info.plist
rename to tests/cefsimple/mac/helper-Info.plist.in
index 22dd18f53..0bdc1d4ab 100644
--- a/tests/cefsimple/mac/helper-Info.plist
+++ b/tests/cefsimple/mac/helper-Info.plist.in
@@ -18,6 +18,10 @@
 	<string>APPL</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/cefsimple/cefsimple.exe.manifest b/tests/cefsimple/win/cefsimple.exe.manifest
similarity index 100%
rename from tests/cefsimple/cefsimple.exe.manifest
rename to tests/cefsimple/win/cefsimple.exe.manifest
diff --git a/tests/cefsimple/res/cefsimple.ico b/tests/cefsimple/win/cefsimple.ico
similarity index 100%
rename from tests/cefsimple/res/cefsimple.ico
rename to tests/cefsimple/win/cefsimple.ico
diff --git a/tests/cefsimple/cefsimple.rc b/tests/cefsimple/win/cefsimple.rc
similarity index 87%
rename from tests/cefsimple/cefsimple.rc
rename to tests/cefsimple/win/cefsimple.rc
index c4f3f2973..3afb68538 100644
--- a/tests/cefsimple/cefsimple.rc
+++ b/tests/cefsimple/win/cefsimple.rc
@@ -1,79 +1,79 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#define APSTUDIO_HIDDEN_SYMBOLS
-#include "windows.h"
-#undef APSTUDIO_HIDDEN_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (U.S.) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-#ifdef _WIN32
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-#endif //_WIN32
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Icon
-//
-
-// Icon with lowest ID value placed first to ensure application icon
-// remains consistent on all systems.
-IDI_CEFSIMPLE           ICON                    "res\cefsimple.ico"
-IDI_SMALL               ICON                    "res\small.ico"
-
-
-#ifdef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// TEXTINCLUDE
-//
-
-1 TEXTINCLUDE
-BEGIN
-    "resource.h\0"
-END
-
-2 TEXTINCLUDE
-BEGIN
-    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
-    "#include ""windows.h""\r\n"
-    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
-    "\0"
-END
-
-3 TEXTINCLUDE
-BEGIN
-    "\r\n"
-    "\0"
-END
-
-#endif    // APSTUDIO_INVOKED
-
-
-#endif    // English (U.S.) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-
-
-/////////////////////////////////////////////////////////////////////////////
-#endif    // not APSTUDIO_INVOKED
-
+// Microsoft Visual C++ generated resource script.
+//
+#include "tests/cefsimple/resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_CEFSIMPLE           ICON                    "cefsimple.ico"
+IDI_SMALL               ICON                    "small.ico"
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
+    "#include ""windows.h""\r\n"
+    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/tests/cefsimple/res/small.ico b/tests/cefsimple/win/small.ico
similarity index 100%
rename from tests/cefsimple/res/small.ico
rename to tests/cefsimple/win/small.ico
diff --git a/tests/ceftests/CMakeLists.txt.in b/tests/ceftests/CMakeLists.txt.in
index 3d8703538..24d8920b1 100644
--- a/tests/ceftests/CMakeLists.txt.in
+++ b/tests/ceftests/CMakeLists.txt.in
@@ -22,6 +22,7 @@
     'ceftests_sources_linux:LINUX',
     'ceftests_sources_mac:MAC',
     'ceftests_sources_win:WINDOWS',
+    'ceftests_sources_resources_win_rc:WINDOWS',
   ],
 }}
 
@@ -34,6 +35,7 @@
     'shared_sources_mac_helper:MAC',
     'shared_sources_renderer',
     'ceftests_sources_mac_helper:MAC',
+    'ceftests_sources_mac_helper_shared:MAC',
   ],
 }}
 
@@ -145,7 +147,7 @@ if(OS_MAC)
   add_dependencies(${CEF_TARGET} libcef_dll_wrapper cef_gtest)
   target_link_libraries(${CEF_TARGET} libcef_dll_wrapper cef_gtest ${CEF_STANDARD_LIBS})
   set_target_properties(${CEF_TARGET} PROPERTIES
-    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/Info.plist
+    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist.in
     )
 
   # Copy the CEF framework into the Frameworks directory.
@@ -175,7 +177,7 @@ if(OS_MAC)
     # MACOSX_BUNDLE_INFO_PLIST) uses global env variables and would insert the
     # wrong values with multiple targets.
     set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/helper-Info${_target_suffix}.plist")
-    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/mac/helper-Info.plist" _plist_contents)
+    file(READ "${CMAKE_CURRENT_SOURCE_DIR}/mac/helper-Info.plist.in" _plist_contents)
     string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents ${_plist_contents})
     string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents ${_plist_contents})
@@ -214,7 +216,7 @@ if(OS_MAC)
   # directive but that doesn't properly handle nested resource directories.
   # Remove these prefixes from input file paths.
   set(PREFIXES
-    "resources/mac/"
+    "mac/"
     "resources/"
     "../shared/resources/"
     )
@@ -246,7 +248,7 @@ if(OS_WINDOWS)
   endif()
 
   # Add the custom manifest files to the executable.
-  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}/resources/win" "${CEF_TARGET}" "exe")
+  ADD_WINDOWS_MANIFEST("${CMAKE_CURRENT_SOURCE_DIR}/win" "${CEF_TARGET}" "exe")
 
   # Copy CEF binary and resource files to the target output directory.
   COPY_FILES("${CEF_TARGET}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR}" "${CEF_TARGET_OUT_DIR}")
diff --git a/tests/ceftests/resources/mac/English.lproj/InfoPlist.strings b/tests/ceftests/mac/English.lproj/InfoPlist.strings
similarity index 100%
rename from tests/ceftests/resources/mac/English.lproj/InfoPlist.strings
rename to tests/ceftests/mac/English.lproj/InfoPlist.strings
diff --git a/tests/ceftests/resources/mac/English.lproj/MainMenu.xib b/tests/ceftests/mac/English.lproj/MainMenu.xib
similarity index 100%
rename from tests/ceftests/resources/mac/English.lproj/MainMenu.xib
rename to tests/ceftests/mac/English.lproj/MainMenu.xib
diff --git a/tests/ceftests/resources/mac/Info.plist b/tests/ceftests/mac/Info.plist.in
similarity index 90%
rename from tests/ceftests/resources/mac/Info.plist
rename to tests/ceftests/mac/Info.plist.in
index d818386c9..57ffd3756 100644
--- a/tests/ceftests/resources/mac/Info.plist
+++ b/tests/ceftests/mac/Info.plist.in
@@ -19,7 +19,9 @@
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.0</string>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/ceftests/resources/mac/ceftests.icns b/tests/ceftests/mac/ceftests.icns
similarity index 100%
rename from tests/ceftests/resources/mac/ceftests.icns
rename to tests/ceftests/mac/ceftests.icns
diff --git a/tests/ceftests/resources/mac/helper-Info.plist b/tests/ceftests/mac/helper-Info.plist.in
similarity index 88%
rename from tests/ceftests/resources/mac/helper-Info.plist
rename to tests/ceftests/mac/helper-Info.plist.in
index 131745a9b..3308b748a 100644
--- a/tests/ceftests/resources/mac/helper-Info.plist
+++ b/tests/ceftests/mac/helper-Info.plist.in
@@ -18,6 +18,10 @@
 	<string>APPL</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>${VERSION_SHORT}</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${VERSION_SHORT}</string>
 	<key>LSEnvironment</key>
 	<dict>
 		<key>MallocNanoZone</key>
diff --git a/tests/ceftests/resources/win/ceftests.exe.manifest b/tests/ceftests/win/ceftests.exe.manifest
similarity index 100%
rename from tests/ceftests/resources/win/ceftests.exe.manifest
rename to tests/ceftests/win/ceftests.exe.manifest
diff --git a/tests/ceftests/resources/win/ceftests.ico b/tests/ceftests/win/ceftests.ico
similarity index 100%
rename from tests/ceftests/resources/win/ceftests.ico
rename to tests/ceftests/win/ceftests.ico
diff --git a/tests/ceftests/resources/win/ceftests.rc b/tests/ceftests/win/ceftests.rc
similarity index 89%
rename from tests/ceftests/resources/win/ceftests.rc
rename to tests/ceftests/win/ceftests.rc
index 8fe40e80c..7f293aa7f 100644
--- a/tests/ceftests/resources/win/ceftests.rc
+++ b/tests/ceftests/win/ceftests.rc
@@ -29,11 +29,11 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 // Binary
 //
 
-IDS_OSRTEST_HTML BINARY "..\\..\\..\\shared\\resources\\osr_test.html"
-IDS_PDF_HTML BINARY "..\\..\\..\\shared\\resources\\pdf.html"
-IDS_PDF_PDF BINARY "..\\..\\..\\shared\\resources\\pdf.pdf"
-IDS_WINDOW_ICON_1X_PNG BINARY "..\\..\\..\\shared\\resources\\window_icon.1x.png"
-IDS_WINDOW_ICON_2X_PNG BINARY "..\\..\\..\\shared\\resources\\window_icon.2x.png"
+IDS_OSRTEST_HTML BINARY "tests\\shared\\resources\\osr_test.html"
+IDS_PDF_HTML BINARY "tests\\shared\\resources\\pdf.html"
+IDS_PDF_PDF BINARY "tests\\shared\\resources\\pdf.pdf"
+IDS_WINDOW_ICON_1X_PNG BINARY "tests\\shared\\resources\\window_icon.1x.png"
+IDS_WINDOW_ICON_2X_PNG BINARY "tests\\shared\\resources\\window_icon.2x.png"
 
 /////////////////////////////////////////////////////////////////////////////
 //
diff --git a/tests/ceftests/resources/win/small.ico b/tests/ceftests/win/small.ico
similarity index 100%
rename from tests/ceftests/resources/win/small.ico
rename to tests/ceftests/win/small.ico
diff --git a/tools/bazel_util.py b/tools/bazel_util.py
new file mode 100644
index 000000000..601ec9696
--- /dev/null
+++ b/tools/bazel_util.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+QUIET = False
+
+
+def bazel_set_quiet(quiet):
+  global QUIET
+  QUIET = quiet
+
+
+LAST_ERROR = None
+
+
+def bazel_last_error():
+  return LAST_ERROR
+
+
+def _set_last_error(error):
+  global LAST_ERROR
+  LAST_ERROR = error
+
+
+def _print(label, data, pos, msg):
+  data_to_pos = data[0:pos]
+  line = data_to_pos.count('\n')
+  pos_in_line = (pos - data_to_pos.rfind('\n') - 1) if line > 0 else pos
+
+  _set_last_error('ERROR: %s: %s at line %d:%d' % (label, msg, line + 1,
+                                                   pos_in_line + 1))
+  if not QUIET:
+    print(LAST_ERROR)
+
+
+def bazel_substitute(data,
+                     variables,
+                     var_delim_start='${',
+                     var_delim_end='}',
+                     path_list_delim=',\n    ',
+                     path_relative_to=None,
+                     path_abs_prefix='//',
+                     label='data'):
+  _set_last_error(None)
+
+  if not path_relative_to is None and len(path_relative_to) == 0:
+    path_relative_to = None
+  if not path_relative_to is None and path_relative_to[-1] != '/':
+    path_relative_to += '/'
+
+  result = ''
+
+  epos = 0
+  spos = data.find(var_delim_start, epos)
+  while spos >= 0:
+    result += data[epos:spos]
+    epos = data.find(var_delim_end, spos + len(var_delim_start))
+    if epos > spos:
+      sub = ''
+      var = data[spos + len(var_delim_start):epos]
+      if var in variables:
+        val = variables[var]
+        if isinstance(val, list):
+          # Assumed to be a list of file paths.
+          paths = []
+          for path in val:
+            if not path_relative_to is None and path.startswith(
+                path_relative_to):
+              # Use only the remainder of the path.
+              path = path[len(path_relative_to):]
+            else:
+              # Use a fully qualified path.
+              path = path_abs_prefix + path
+            paths.append('"%s"' % path)
+          sub = path_list_delim.join(paths)
+        else:
+          # Direct replacement.
+          sub = str(val)
+      else:
+        _print(label, data, spos,
+               'No value for "%s%s%s"' % (var_delim_start, var, var_delim_end))
+      if len(sub) > 0:
+        result += sub
+      epos += len(var_delim_end)
+    else:
+      _print(label, data, spos,
+             'Missing close bracket for "%s..."' % (data[spos:spos + 5]))
+      # Can't parse any further.
+      break
+    spos = data.find(var_delim_start, epos)
+
+  if epos >= 0:
+    result += data[epos:]
+
+  return result
+
+
+# Test the module.
+if __name__ == "__main__":
+  # Don't print error messages.
+  bazel_set_quiet(True)
+
+  # No substitution
+  assert (bazel_substitute('foobar', {}) == 'foobar')
+  assert (bazel_last_error() == None)
+
+  # No matching variable
+  assert (bazel_substitute('${a}foobar', {}) == 'foobar')
+  assert (bazel_last_error() == 'ERROR: data: No value for "${a}" at line 1:1')
+  assert (bazel_substitute('\nfoo${a}bar', {}) == '\nfoobar')
+  assert (bazel_last_error() == 'ERROR: data: No value for "${a}" at line 2:4')
+  assert (bazel_substitute('\n\nfoobar${a}', {}) == '\n\nfoobar')
+  assert (bazel_last_error() == 'ERROR: data: No value for "${a}" at line 3:7')
+
+  # Missing end bracket.
+  assert (bazel_substitute('${afoobar', {}) == '')
+  assert (bazel_last_error() ==
+          'ERROR: data: Missing close bracket for "${afo..." at line 1:1')
+  assert (bazel_substitute('\nfoo${abar', {}) == '\nfoo')
+  assert (bazel_last_error() ==
+          'ERROR: data: Missing close bracket for "${aba..." at line 2:4')
+  assert (bazel_substitute('\n\nfoobar${a', {}) == '\n\nfoobar')
+  assert (bazel_last_error() ==
+          'ERROR: data: Missing close bracket for "${a..." at line 3:7')
+
+  # Variable substitution
+  assert (bazel_substitute('foo${choo}bar', {'choo': 'blah'}) == 'fooblahbar')
+  assert (bazel_last_error() == None)
+  assert (bazel_substitute('${ah}${choo}bar${ba}',
+                           {'ah': 'foo',
+                            'choo': 5,
+                            'ba': ''}) == 'foo5bar')
+  assert (bazel_last_error() == None)
+
+  # Custom variable delimiters.
+  assert (bazel_substitute(
+      '$$ah$$$$choo$$bar$$ba$$', {'ah': 'foo',
+                                  'choo': 5,
+                                  'ba': ''},
+      var_delim_start='$$',
+      var_delim_end='$$') == 'foo5bar')
+  assert (bazel_last_error() == None)
+
+  paths = [
+      'path/to/a.ext',
+      'path/to/b.ext',
+      'another/path/c.ext',
+  ]
+
+  # All absolute paths.
+  assert (bazel_substitute('[${paths}]', {'paths': paths}, path_list_delim=',')
+          == '["//path/to/a.ext","//path/to/b.ext","//another/path/c.ext"]')
+  assert (bazel_last_error() == None)
+
+  # Some relative paths.
+  assert (bazel_substitute(
+      '[${paths}]', {'paths': paths},
+      path_list_delim=',',
+      path_relative_to='path/to') == '["a.ext","b.ext","//another/path/c.ext"]')
+  assert (bazel_last_error() == None)
+
+  print('Tests passed!')
diff --git a/tools/cef_version.py b/tools/cef_version.py
index 132438bac..7bc502679 100644
--- a/tools/cef_version.py
+++ b/tools/cef_version.py
@@ -230,11 +230,16 @@ class VersionFormatter:
       return self._get_old_version_parts()
     return self._get_version_parts()
 
+  def get_short_version_string(self, oldFormat=None):
+    """ Returns the short CEF version number string based on current checkout
+        state. """
+    parts = self.get_version_parts(oldFormat=oldFormat)
+    return "%d.%d.%d" % (parts['MAJOR'], parts['MINOR'], parts['PATCH'])
+
   def get_plist_version_string(self, oldFormat=None):
     """ Returns the CEF version number string for plist files based on current
         checkout state. """
-    parts = self.get_version_parts(oldFormat=oldFormat)
-    return "%d.%d.%d.0" % (parts['MAJOR'], parts['MINOR'], parts['PATCH'])
+    return self.get_short_version_string() + ".0"
 
   def get_dylib_version_string(self, oldFormat=None):
     """ Returns the CEF version number string for dylib files based on current
diff --git a/tools/distrib/bazel/.bazelrc b/tools/distrib/bazel/.bazelrc
new file mode 100755
index 000000000..494c77333
--- /dev/null
+++ b/tools/distrib/bazel/.bazelrc
@@ -0,0 +1,54 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Enable Bzlmod for every Bazel command.
+common --enable_bzlmod
+
+# Enable build:{macos,linux,windows}.
+build --enable_platform_specific_config
+
+#
+# Common configuration.
+#
+
+# Build with C++17.
+build:linux --cxxopt='-std=c++17'
+build:macos --cxxopt='-std=c++17'
+build:macos --copt='-std=c++17'
+build:windows --cxxopt='/std:c++17'
+
+#
+# MacOS configuration.
+#
+
+build:macos --copt='-ObjC++'
+
+#
+# Windows configuration.
+#
+
+# Enable creation of symlinks for runfiles.
+build:windows --enable_runfiles
+
+# Use /MT[d].
+build:windows --features=static_link_msvcrt
+
+#
+# Linux configuration.
+#
+
+# The cfi-icall attribute is not supported by the GNU C++ compiler.
+# TODO: Move to toolchain or add `--config=[gcc|llvm]` command-line option.
+build:linux --cxxopt=-Wno-attributes
+
+# Use hardlinks instead of symlinks in sandboxes on Linux.
+# This is required for CEF binaries to run, and for copy_filegroups() to work
+# as expected on Linux.
+build:linux --experimental_use_hermetic_linux_sandbox
+build:linux --sandbox_add_mount_pair=/etc
+build:linux --sandbox_add_mount_pair=/usr
+ ## symlinks into /usr
+build:linux --sandbox_add_mount_pair=/usr/bin:/bin
+build:linux --sandbox_add_mount_pair=/usr/lib:/lib
+build:linux --sandbox_add_mount_pair=/usr/lib64:/lib64
diff --git a/tools/distrib/bazel/.bazelversion b/tools/distrib/bazel/.bazelversion
new file mode 100644
index 000000000..21c8c7b46
--- /dev/null
+++ b/tools/distrib/bazel/.bazelversion
@@ -0,0 +1 @@
+7.1.1
diff --git a/tools/distrib/bazel/BUILD.bazel b/tools/distrib/bazel/BUILD.bazel
new file mode 100755
index 000000000..c433e3a65
--- /dev/null
+++ b/tools/distrib/bazel/BUILD.bazel
@@ -0,0 +1,366 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
+
+load("@bazel_skylib//lib:selects.bzl", "selects")
+load("@build_bazel_rules_apple//apple:apple.bzl", "apple_dynamic_framework_import")
+load("//bazel/win:variables.bzl",
+     WIN_DLLS="DLLS",
+     WIN_DLLS_X64="DLLS_X64",
+     WIN_SANDBOX_LIBS="SANDBOX_LIBS",
+     WIN_COMMON_COPTS="COMMON_COPTS",
+     WIN_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     WIN_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG",
+     WIN_COMMON_DEFINES="COMMON_DEFINES",
+     WIN_COMMON_DEFINES_RELEASE="COMMON_DEFINES_RELEASE",
+     WIN_COMMON_DEFINES_DEBUG="COMMON_DEFINES_DEBUG")
+load("//bazel/linux:variables.bzl",
+     LINUX_SOS="SOS",
+     LINUX_COMMON_COPTS="COMMON_COPTS",
+     LINUX_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     LINUX_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG",
+     LINUX_COMMON_DEFINES="COMMON_DEFINES",
+     LINUX_COMMON_DEFINES_RELEASE="COMMON_DEFINES_RELEASE",
+     LINUX_COMMON_DEFINES_DEBUG="COMMON_DEFINES_DEBUG")
+load("//bazel/mac:variables.bzl",
+     "CEF_FRAMEWORK_NAME",
+     MAC_COMMON_COPTS="COMMON_COPTS",
+     MAC_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     MAC_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG",
+     MAC_COMMON_DEFINES="COMMON_DEFINES",
+     MAC_COMMON_DEFINES_RELEASE="COMMON_DEFINES_RELEASE",
+     MAC_COMMON_DEFINES_DEBUG="COMMON_DEFINES_DEBUG")
+load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library", "objc_library")
+
+#
+# Define supported configurations.
+# See https://bazel.build/docs/configurable-attributes
+#
+# Normal build (ARM64 host):
+# % bazel build //tests/cefsimple [-c dbg]
+#
+# Cross-compile build (ARM64 host):
+# % bazel build //tests/cefsimple --cpu=darwin_x86_64 [-c dbg]
+#
+
+config_setting(
+    name = "dbg",
+    values = {"compilation_mode": "dbg"},
+)
+
+config_setting(
+    name = "fastbuild",
+    values = {"compilation_mode": "fastbuild"},
+)
+
+config_setting(
+    name = "opt",
+    values = {"compilation_mode": "opt"},
+)
+
+selects.config_setting_group(
+    name = "windows_32",
+    match_all = ["@platforms//os:windows", "@platforms//cpu:x86_32"],
+)
+
+selects.config_setting_group(
+    name = "windows_64",
+    match_all = ["@platforms//os:windows", "@platforms//cpu:x86_64"],
+)
+
+selects.config_setting_group(
+    name = "windows_dbg",
+    match_all = ["@platforms//os:windows", ":dbg"],
+)
+
+selects.config_setting_group(
+    name = "windows_fastbuild",
+    match_all = ["@platforms//os:windows", ":fastbuild"],
+)
+
+selects.config_setting_group(
+    name = "windows_opt",
+    match_all = ["@platforms//os:windows", ":opt"],
+)
+
+selects.config_setting_group(
+    name = "linux_dbg",
+    match_all = ["@platforms//os:linux", ":dbg"],
+)
+
+selects.config_setting_group(
+    name = "linux_fastbuild",
+    match_all = ["@platforms//os:linux", ":fastbuild"],
+)
+
+selects.config_setting_group(
+    name = "linux_opt",
+    match_all = ["@platforms//os:linux", ":opt"],
+)
+
+selects.config_setting_group(
+    name = "macos_dbg",
+    match_all = ["@platforms//os:macos", ":dbg"],
+)
+
+selects.config_setting_group(
+    name = "macos_fastbuild",
+    match_all = ["@platforms//os:macos", ":fastbuild"],
+)
+
+selects.config_setting_group(
+    name = "macos_opt",
+    match_all = ["@platforms//os:macos", ":opt"],
+)
+
+#
+# Define common build targets.
+#
+
+# Public headers for cef_wrapper here.
+# Seperated from the actual cef_wrapper since the *.mm file needs access to these as well.
+cc_library(
+    name = "cef_wrapper_headers",
+    hdrs = glob(
+        [
+            "include/**/*.h",
+        ],
+        exclude = [
+            "include/base/internal/**/*.*",
+            "include/internal/**/*.*",
+        ],
+    ),
+)
+
+objc_library(
+    name = "cef_wrapper_apple",
+    srcs = [
+        "libcef_dll/wrapper/cef_library_loader_mac.mm",
+    ],
+    implementation_deps = [":cef_wrapper_headers"],
+)
+
+cc_library(
+    name = "cef_wrapper",
+    srcs = glob(
+        [
+            "libcef_dll/**/*.cc",
+            "libcef_dll/**/*.h",
+            "libcef_dll_wrapper/**/*.cc",
+            "libcef_dll_wrapper/**/*.h",
+            "include/base/internal/**/*.h",
+            "include/base/internal/**/*.inc",
+            "include/internal/**/*.h",
+            "include/test/*.h",
+        ],
+    ),
+    copts = select({
+        "@platforms//os:windows": WIN_COMMON_COPTS,
+        "@platforms//os:linux": LINUX_COMMON_COPTS,
+        "@platforms//os:macos": MAC_COMMON_COPTS,
+        "//conditions:default": None,
+    }) + select({
+        ":windows_opt": WIN_COMMON_COPTS_RELEASE,
+        ":windows_dbg": WIN_COMMON_COPTS_DEBUG,
+        ":windows_fastbuild": WIN_COMMON_COPTS_RELEASE,
+        ":linux_opt": LINUX_COMMON_COPTS_RELEASE,
+        ":linux_dbg": LINUX_COMMON_COPTS_DEBUG,
+        ":linux_fastbuild": LINUX_COMMON_COPTS_RELEASE,
+        ":macos_opt": MAC_COMMON_COPTS_RELEASE,
+        ":macos_dbg": MAC_COMMON_COPTS_DEBUG,
+        ":macos_fastbuild": MAC_COMMON_COPTS_RELEASE,
+        "//conditions:default": None,
+    }),
+    defines = [
+        "WRAPPING_CEF_SHARED",
+    ] + select({
+        "@platforms//os:windows": WIN_COMMON_DEFINES,
+        "@platforms//os:linux": LINUX_COMMON_DEFINES,
+        "@platforms//os:macos": MAC_COMMON_DEFINES,
+        "//conditions:default": None,
+    }) + select({
+        ":windows_opt": WIN_COMMON_DEFINES_RELEASE,
+        ":windows_dbg": WIN_COMMON_DEFINES_DEBUG,
+        ":windows_fastbuild": WIN_COMMON_DEFINES_RELEASE,
+        ":linux_opt": LINUX_COMMON_DEFINES_RELEASE,
+        ":linux_dbg": LINUX_COMMON_DEFINES_DEBUG,
+        ":linux_fastbuild": LINUX_COMMON_DEFINES_RELEASE,
+        ":macos_opt": MAC_COMMON_DEFINES_RELEASE,
+        ":macos_dbg": MAC_COMMON_DEFINES_DEBUG,
+        ":macos_fastbuild": MAC_COMMON_DEFINES_RELEASE,
+        "//conditions:default": None,
+    }),
+    deps = [":cef_wrapper_headers"] +
+           select({
+               "@platforms//os:macos": [":cef_wrapper_apple"],
+               "@platforms//os:windows": [":cef"],
+               "//conditions:default": None,
+           }),
+)
+
+# Only available on MacOS/Windows.
+cc_library(
+    name = "cef_sandbox_linkflags",
+    linkopts = select({
+        "@platforms//os:macos": ["-lsandbox"],
+        "@platforms//os:windows": [
+            "/DEFAULTLIB:{}".format(lib) for lib in WIN_SANDBOX_LIBS
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+cc_import(
+    name = "cef_sandbox_debug",
+    static_library = select({
+        "@platforms//os:macos": "Debug/cef_sandbox.a",
+        "@platforms//os:windows": "Debug/cef_sandbox.lib",
+        "//conditions:default": None,
+    }),
+    deps = [":cef_sandbox_linkflags"],
+)
+
+cc_import(
+    name = "cef_sandbox_release",
+    static_library = select({
+        "@platforms//os:macos": "Release/cef_sandbox.a",
+        "@platforms//os:windows": "Release/cef_sandbox.lib",
+        "//conditions:default": None,
+    }),
+    deps = [":cef_sandbox_linkflags"],
+)
+
+alias(
+    name = "cef_sandbox",
+    actual = select({
+        "//:dbg": ":cef_sandbox_debug",
+        "//conditions:default": ":cef_sandbox_release",
+    }),
+)
+
+filegroup(
+    name = "dlls_opt",
+    srcs = ["Release/{}".format(name) for name in WIN_DLLS] +
+        select({
+            "//:windows_64": ["Release/{}".format(name) for name in WIN_DLLS_X64],
+            "//conditions:default": None,
+        }),
+)
+
+filegroup(
+    name = "dlls_dbg",
+    srcs = ["Debug/{}".format(name) for name in WIN_DLLS] +
+        select({
+            "//:windows_64": ["Debug/{}".format(name) for name in WIN_DLLS_X64],
+            "//conditions:default": None,
+        }),
+)
+
+alias(
+    name = "dlls",
+    actual = select({
+        "//:dbg": ":dlls_dbg",
+        "//conditions:default": ":dlls_opt",
+    })
+)
+
+filegroup(
+    name = "sos_opt",
+    srcs = ["Release/{}".format(name) for name in LINUX_SOS],
+)
+
+filegroup(
+    name = "sos_dbg",
+    srcs = ["Debug/{}".format(name) for name in LINUX_SOS],
+)
+
+alias(
+    name = "sos",
+    actual = select({
+        "//:dbg": ":sos_dbg",
+        "//conditions:default": ":sos_opt",
+    })
+)
+
+filegroup(
+    name = "resources_common",
+    srcs = glob([
+        "Resources/**",
+    ]),
+)
+
+filegroup(
+    name = "resources_opt",
+    srcs = [
+        "Release/snapshot_blob.bin",
+        "Release/v8_context_snapshot.bin",
+        "Release/vk_swiftshader_icd.json",
+        ":resources_common",
+    ],
+)
+
+filegroup(
+    name = "resources_dbg",
+    srcs = [
+        "Debug/snapshot_blob.bin",
+        "Debug/v8_context_snapshot.bin",
+        "Debug/vk_swiftshader_icd.json",
+        ":resources_common",
+    ],
+)
+
+alias(
+    name = "resources",
+    actual = select({
+        "//:opt": ":resources_opt",
+        "//conditions:default": ":resources_dbg",
+    })
+)
+
+# Only available on Linux/Windows.
+cc_import(
+    name = "cef_dbg",
+    interface_library = select({
+        "@platforms//os:windows": "Debug/libcef.lib",
+        "//conditions:default": None,
+    }),
+    shared_library = select({
+        "@platforms//os:linux": "Debug/libcef.so",
+        "@platforms//os:windows": "Debug/libcef.dll",
+        "//conditions:default": None,
+    }),
+)
+
+cc_import(
+    name = "cef_opt",
+    interface_library = select({
+        "@platforms//os:windows": "Release/libcef.lib",
+        "//conditions:default": None,
+    }),
+    shared_library = select({
+        "@platforms//os:linux": "Release/libcef.so",
+        "@platforms//os:windows": "Release/libcef.dll",
+        "//conditions:default": None,
+    }),
+)
+
+alias(
+    name = "cef",
+    actual = select({
+        "//:dbg": ":cef_dbg",
+        "//conditions:default": ":cef_opt",
+    }),
+)
+
+apple_dynamic_framework_import(
+    name = "cef_framework",
+    framework_imports = select({
+        "//:dbg": glob(["Debug/{}.framework/**".format(CEF_FRAMEWORK_NAME)]),
+        "//conditions:default": glob(["Release/{}.framework/**".format(CEF_FRAMEWORK_NAME)]),
+    }),
+)
diff --git a/tools/distrib/bazel/MODULE.bazel.in b/tools/distrib/bazel/MODULE.bazel.in
new file mode 100644
index 000000000..24667b8c6
--- /dev/null
+++ b/tools/distrib/bazel/MODULE.bazel.in
@@ -0,0 +1,17 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+module(name = "cef", version = "${version_short}")
+
+# Configure local MacOS toolchain.
+# See https://github.com/bazelbuild/apple_support/releases
+bazel_dep(name = "apple_support", version = "1.16.0", repo_name = "build_bazel_apple_support")
+# See https://github.com/bazelbuild/rules_apple/releases
+bazel_dep(name = "rules_apple", version = "3.6.0", repo_name = "build_bazel_rules_apple")
+
+# Configure local C++ toolchain.
+# See https://github.com/bazelbuild/rules_cc/releases
+bazel_dep(name = "rules_cc", version = "0.0.9")
+
+# Add other dependencies here.
diff --git a/tools/distrib/bazel/WORKSPACE b/tools/distrib/bazel/WORKSPACE
new file mode 100755
index 000000000..6e8562847
--- /dev/null
+++ b/tools/distrib/bazel/WORKSPACE
@@ -0,0 +1,55 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+workspace(name = "cef")
+
+#
+# Windows configuration.
+#
+
+# Configure windows SDK.
+load("//bazel/win:setup_sdk.bzl", "setup_sdk")
+setup_sdk(name = "winsdk")
+
+#
+# Linux configuration.
+#
+
+# Configure Linux using pkg-config.
+local_repository(name="pkg_config", path="bazel/linux/pkg_config")
+load("@pkg_config//:pkg_config.bzl", "pkg_config")
+
+# Define packages used by cefclient.
+
+pkg_config(
+    name = "gmodule2",
+    pkg_name = "gmodule-2.0",
+)
+
+pkg_config(
+    name = "gtk3",
+    pkg_name = "gtk+-3.0",
+)
+
+pkg_config(
+    name = "gthread2",
+    pkg_name = "gthread-2.0",
+)
+
+pkg_config(
+    name = "gtkprint3",
+    pkg_name = "gtk+-unix-print-3.0",
+)
+
+pkg_config(
+    name = "xi",
+)
+
+# Define packages used by ceftests.
+
+pkg_config(
+    name = "glib2",
+    pkg_name = "glib-2.0",
+)
+
diff --git a/tools/distrib/bazel/bazel-variables.bzl.in b/tools/distrib/bazel/bazel-variables.bzl.in
new file mode 100644
index 000000000..6ae0c33f6
--- /dev/null
+++ b/tools/distrib/bazel/bazel-variables.bzl.in
@@ -0,0 +1,8 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+# CEF version.
+VERSION_LONG="${version_long}"
+VERSION_SHORT="${version_short}"
+VERSION_PLIST="${version_plist}"
diff --git a/tools/distrib/bazel/tests-cefclient-BUILD.bazel.in b/tools/distrib/bazel/tests-cefclient-BUILD.bazel.in
new file mode 100644
index 000000000..14358dd3d
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefclient-BUILD.bazel.in
@@ -0,0 +1,128 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefclient"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from subpackages only.
+package(default_visibility = [
+    ":__subpackages__",
+])
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "objc_library")
+
+#
+# Source file lists.
+#
+
+srcs_common = [
+    ${cefclient_sources_common}
+]
+
+srcs_renderer = [
+    ${cefclient_sources_renderer}
+]
+
+srcs_browser = [
+    ${cefclient_sources_browser}
+]
+
+srcs_browser_linux = [
+    ${cefclient_sources_linux}
+]
+
+srcs_browser_mac = [
+    ${cefclient_sources_mac}
+]
+
+srcs_browser_win = [
+    ${cefclient_sources_win}
+]
+
+srcs_resources = [
+    ${cefclient_sources_resources}
+]
+
+filegroup(
+    name = "Resources",
+    srcs = srcs_resources,
+)
+
+#
+# MacOS targets.
+#
+
+objc_library(
+    name = "BrowserLibMac",
+    srcs = srcs_common + srcs_browser + srcs_browser_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        "//tests/shared:BrowserLibMac",
+    ],
+)
+
+objc_library(
+    name = "RendererLibMac",
+    srcs = srcs_common + srcs_renderer,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        "//tests/shared:RendererLibMac",
+    ],
+)
+
+#
+# Windows targets.
+#
+
+# Allow access from the declare_exe target.
+filegroup(
+    name = "ResourceH",
+    srcs = [
+        "browser/resource.h"
+    ]
+)
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration and avoids issues with Windows discarding symbols (like WinMain)
+# when linking libraries.
+filegroup(
+    name = "SrcsWin",
+    srcs = srcs_common + srcs_browser + srcs_browser_win + srcs_renderer,
+    target_compatible_with = [
+        "@platforms//os:windows",
+    ],
+)
+
+#
+# Linux targets.
+#
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration.
+filegroup(
+    name = "SrcsLinux",
+    srcs = srcs_common + srcs_browser + srcs_browser_linux + srcs_renderer,
+    target_compatible_with = [
+        "@platforms//os:linux",
+    ],
+)
+
+#
+# Alias to platform-specific build targets.
+#
+
+alias(
+    name = PRODUCT_NAME,
+    actual = select({
+        "@platforms//os:linux": "{}/linux:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:macos": "{}/mac:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:windows": "{}/win:{}".format(PKG_NAME, PRODUCT_NAME),
+    }),
+)
diff --git a/tools/distrib/bazel/tests-cefclient-linux-BUILD.bazel b/tools/distrib/bazel/tests-cefclient-linux-BUILD.bazel
new file mode 100644
index 000000000..a8bbf253d
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefclient-linux-BUILD.bazel
@@ -0,0 +1,59 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefclient"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel:copy_filegroups.bzl", "copy_filegroups")
+load("//bazel/linux:exe_helpers.bzl", "declare_exe")
+
+#
+# Linux executable target.
+#
+
+# Copy resources into the current project.
+copy_target = "{}_resources".format(PRODUCT_NAME)
+copy_filegroups(
+    name = copy_target,
+    filegroups = [
+        "{}:Resources".format(PKG_NAME),
+        "//tests/shared:Resources",
+    ],
+    remove_prefixes = [
+        "tests/{}/resources".format(PRODUCT_NAME),
+        "tests/shared/resources",
+    ],
+    add_prefix = "cefclient_files",
+)
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsLinux".format(PKG_NAME),
+        "//tests/shared:SrcsLinux",
+    ],
+    data = [
+        ":{}".format(copy_target),
+    ],
+    copts = [
+         "-Wno-deprecated-declarations",
+    ],
+    linkopts = [
+        "-lGL",
+    ],
+    deps = [
+        # Declared in the top-level WORKSPACE file.
+        "@gmodule2//:lib",
+        "@gtk3//:lib",
+        "@gthread2//:lib",
+        "@gtkprint3//:lib",
+        "@xi//:lib",
+    ],
+)
+
diff --git a/tools/distrib/bazel/tests-cefclient-mac-BUILD.bazel b/tools/distrib/bazel/tests-cefclient-mac-BUILD.bazel
new file mode 100644
index 000000000..9bb31c959
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefclient-mac-BUILD.bazel
@@ -0,0 +1,53 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefclient"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/mac:app_helpers.bzl", "declare_all_helper_apps", "declare_main_app")
+
+#
+# MacOS app bundle target.
+#
+
+filegroup(
+    name = "ResourcesMac",
+    srcs = [
+        "English.lproj/InfoPlist.strings",
+        "English.lproj/MainMenu.xib",
+        "{}.icns".format(PRODUCT_NAME),
+    ],
+)
+
+# Helper app bundles.
+declare_all_helper_apps(
+    name = PRODUCT_NAME,
+    info_plist = "helper-Info.plist.in",
+    deps = [
+        "{}:RendererLibMac".format(PKG_NAME),
+    ],
+)
+
+# Main app bundle.
+declare_main_app(
+    name = PRODUCT_NAME,
+    info_plist = "Info.plist.in",
+    deps = [
+        "{}:BrowserLibMac".format(PKG_NAME),
+    ],
+    linkopts = [
+        "-framework IOSurface",
+        "-framework OpenGL",
+    ],
+    resources = [
+        ":ResourcesMac",
+        "{}:Resources".format(PKG_NAME),
+        "//tests/shared:Resources",
+    ]
+)
diff --git a/tools/distrib/bazel/tests-cefclient-win-BUILD.bazel b/tools/distrib/bazel/tests-cefclient-win-BUILD.bazel
new file mode 100644
index 000000000..94ddb81c1
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefclient-win-BUILD.bazel
@@ -0,0 +1,59 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefclient"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/win:exe_helpers.bzl", "declare_exe")
+
+#
+# Windows executable target.
+#
+
+LINK_LIBS = [
+    "comdlg32.lib",
+    "d3d11.lib",
+    "glu32.lib",
+    "imm32.lib",
+    "opengl32.lib",
+]
+
+DELAYLOAD_DLLS = [
+    "comdlg32.dll",
+    "glu32.dll",
+    "oleaut32.dll",
+    "opengl32.dll",
+]
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsWin".format(PKG_NAME),
+        "//tests/shared:SrcsWin",
+    ],
+    rc_file = "{}.rc".format(PRODUCT_NAME),
+    manifest_srcs = [
+        "compatibility.manifest",
+        "{}.exe.manifest".format(PRODUCT_NAME),
+    ],
+    resources_srcs = [
+        "{}:ResourceH".format(PKG_NAME),
+        "{}:Resources".format(PKG_NAME),
+        "{}.ico".format(PRODUCT_NAME),
+        "small.ico",
+        "//tests/shared:Resources",
+    ],
+    linkopts = [
+        "/SUBSYSTEM:WINDOWS",
+    ] + [
+        "/DEFAULTLIB:{}".format(lib) for lib in LINK_LIBS
+    ] + [
+        "/DELOAYLOAD:{}".format(lib) for lib in DELAYLOAD_DLLS
+    ],
+)
diff --git a/tools/distrib/bazel/tests-cefsimple-BUILD.bazel.in b/tools/distrib/bazel/tests-cefsimple-BUILD.bazel.in
new file mode 100644
index 000000000..689176d97
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefsimple-BUILD.bazel.in
@@ -0,0 +1,113 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefsimple"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from subpackages only.
+package(default_visibility = [
+    ":__subpackages__",
+])
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "objc_library")
+
+#
+# Source file lists.
+#
+
+srcs_browser = [
+    ${cefsimple_sources_common}
+]
+
+srcs_browser_linux = [
+    ${cefsimple_sources_linux}
+]
+
+srcs_browser_mac = [
+    ${cefsimple_sources_mac}
+]
+
+srcs_browser_win = [
+    ${cefsimple_sources_win}
+]
+
+srcs_renderer_mac = [
+    ${cefsimple_sources_mac_helper}
+]
+
+#
+# MacOS targets.
+#
+
+objc_library(
+    name = "BrowserLibMac",
+    srcs = srcs_browser + srcs_browser_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+    ],
+)
+
+cc_library(
+    name = "RendererLibMac",
+    srcs = srcs_renderer_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+    ],
+)
+
+#
+# Windows targets.
+#
+
+# Allow access from the declare_exe target.
+filegroup(
+    name = "ResourceH",
+    srcs = [
+        "resource.h"
+    ]
+)
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration and avoids issues with Windows discarding symbols (like WinMain)
+# when linking libraries.
+filegroup(
+    name = "SrcsWin",
+    srcs = srcs_browser + srcs_browser_win,
+    target_compatible_with = [
+        "@platforms//os:windows",
+    ],
+)
+
+#
+# Linux targets.
+#
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration.
+filegroup(
+    name = "SrcsLinux",
+    srcs = srcs_browser + srcs_browser_linux,
+    target_compatible_with = [
+        "@platforms//os:linux",
+    ],
+)
+
+#
+# Alias to platform-specific build targets.
+#
+
+alias(
+    name = PRODUCT_NAME,
+    actual = select({
+        "@platforms//os:linux": "{}/linux:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:macos": "{}/mac:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:windows": "{}/win:{}".format(PKG_NAME, PRODUCT_NAME),
+    }),
+)
diff --git a/tools/distrib/bazel/tests-cefsimple-linux-BUILD.bazel b/tools/distrib/bazel/tests-cefsimple-linux-BUILD.bazel
new file mode 100644
index 000000000..0da9d56b1
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefsimple-linux-BUILD.bazel
@@ -0,0 +1,25 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefsimple"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/linux:exe_helpers.bzl", "declare_exe")
+
+#
+# Linux executable target.
+#
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsLinux".format(PKG_NAME),
+    ],
+)
+
diff --git a/tools/distrib/bazel/tests-cefsimple-mac-BUILD.bazel b/tools/distrib/bazel/tests-cefsimple-mac-BUILD.bazel
new file mode 100644
index 000000000..88283598d
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefsimple-mac-BUILD.bazel
@@ -0,0 +1,48 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefsimple"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/mac:app_helpers.bzl", "declare_all_helper_apps", "declare_main_app")
+
+#
+# MacOS app bundle target.
+#
+
+filegroup(
+    name = "ResourcesMac",
+    srcs = [
+        "English.lproj/InfoPlist.strings",
+        "English.lproj/MainMenu.xib",
+        "{}.icns".format(PRODUCT_NAME),
+    ],
+)
+
+# Helper app bundles.
+declare_all_helper_apps(
+    name = PRODUCT_NAME,
+    info_plist = "helper-Info.plist.in",
+    deps = [
+        "{}:RendererLibMac".format(PKG_NAME),
+    ],
+)
+
+# Main app bundle.
+declare_main_app(
+    name = PRODUCT_NAME,
+   	info_plist = "Info.plist.in",
+    deps = [
+        "{}:BrowserLibMac".format(PKG_NAME),
+    ],
+    linkopts = [],
+    resources = [
+        ":ResourcesMac",
+    ]
+)
diff --git a/tools/distrib/bazel/tests-cefsimple-win-BUILD.bazel b/tools/distrib/bazel/tests-cefsimple-win-BUILD.bazel
new file mode 100644
index 000000000..6420d7669
--- /dev/null
+++ b/tools/distrib/bazel/tests-cefsimple-win-BUILD.bazel
@@ -0,0 +1,37 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "cefsimple"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/win:exe_helpers.bzl", "declare_exe")
+
+#
+# Windows executable target.
+#
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsWin".format(PKG_NAME),
+    ],
+    rc_file = "{}.rc".format(PRODUCT_NAME),
+    manifest_srcs = [
+        "compatibility.manifest",
+        "{}.exe.manifest".format(PRODUCT_NAME),
+    ],
+    resources_srcs = [
+        "{}:ResourceH".format(PKG_NAME),
+        "{}.ico".format(PRODUCT_NAME),
+        "small.ico",
+    ],
+    linkopts = [
+        "/SUBSYSTEM:WINDOWS",
+    ],
+)
diff --git a/tools/distrib/bazel/tests-ceftests-BUILD.bazel.in b/tools/distrib/bazel/tests-ceftests-BUILD.bazel.in
new file mode 100644
index 000000000..c8e38b99b
--- /dev/null
+++ b/tools/distrib/bazel/tests-ceftests-BUILD.bazel.in
@@ -0,0 +1,128 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "ceftests"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from subpackages only.
+package(default_visibility = [
+    ":__subpackages__",
+])
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "objc_library")
+
+#
+# Source file lists.
+#
+
+srcs_renderer_mac = [
+    ${ceftests_sources_mac_helper}
+]
+
+srcs_browser = [
+    ${ceftests_sources_common}
+]
+
+srcs_browser_linux = [
+    ${ceftests_sources_linux}
+]
+
+srcs_browser_mac = [
+    ${ceftests_sources_mac}
+]
+
+srcs_browser_win = [
+    ${ceftests_sources_win}
+]
+
+filegroup(
+    name = "Resources",
+    srcs = glob(["resources/**"]),
+)
+
+#
+# MacOS targets.
+#
+
+# Copy the 'net' folder into app bundle Resources.
+filegroup(
+    name = "ResourcesMac",
+    srcs = ["resources/net"],
+)
+
+objc_library(
+    name = "BrowserLibMac",
+    srcs = srcs_browser + srcs_browser_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        "//tests/gtest",
+        "//tests/shared:BrowserLibMacCefTests",
+    ],
+)
+
+objc_library(
+    name = "RendererLibMac",
+    srcs = srcs_renderer_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        "//tests/gtest",
+        "//tests/shared:RendererLibMacCefTests",
+    ],
+)
+
+#
+# Windows targets.
+#
+
+# Allow access from the declare_exe target.
+filegroup(
+    name = "ResourceH",
+    srcs = [
+        "resource.h"
+    ]
+)
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration and avoids issues with Windows discarding symbols (like WinMain)
+# when linking libraries.
+filegroup(
+    name = "SrcsWin",
+    srcs = srcs_browser + srcs_browser_win,
+    target_compatible_with = [
+        "@platforms//os:windows",
+    ],
+)
+
+#
+# Linux targets.
+#
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration.
+filegroup(
+    name = "SrcsLinux",
+    srcs = srcs_browser + srcs_browser_linux,
+    target_compatible_with = [
+        "@platforms//os:linux",
+    ],
+)
+
+#
+# Alias to platform-specific build targets.
+#
+
+alias(
+    name = PRODUCT_NAME,
+    actual = select({
+        "@platforms//os:linux": "{}/linux:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:macos": "{}/mac:{}".format(PKG_NAME, PRODUCT_NAME),
+        "@platforms//os:windows": "{}/win:{}".format(PKG_NAME, PRODUCT_NAME),
+    }),
+)
diff --git a/tools/distrib/bazel/tests-ceftests-linux-BUILD.bazel b/tools/distrib/bazel/tests-ceftests-linux-BUILD.bazel
new file mode 100644
index 000000000..31b3c32ac
--- /dev/null
+++ b/tools/distrib/bazel/tests-ceftests-linux-BUILD.bazel
@@ -0,0 +1,53 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "ceftests"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel:copy_filegroups.bzl", "copy_filegroups")
+load("//bazel/linux:exe_helpers.bzl", "declare_exe")
+
+#
+# Linux executable target.
+#
+
+# Copy resources into the current project.
+copy_target = "{}_resources".format(PRODUCT_NAME)
+copy_filegroups(
+    name = copy_target,
+    filegroups = [
+        "{}:Resources".format(PKG_NAME),
+        "//tests/shared:Resources",
+    ],
+    remove_prefixes = [
+        "tests/{}/resources".format(PRODUCT_NAME),
+        "tests/shared/resources",
+    ],
+    add_prefix = "ceftests_files",
+)
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsLinux".format(PKG_NAME),
+        "//tests/shared:SrcsLinux",
+    ],
+    data = [
+        ":{}".format(copy_target),
+    ],
+    copts = [
+         "-Wno-comments",
+    ],
+    deps = [
+        "//tests/gtest",
+        # Declared in the top-level WORKSPACE file.
+        "@glib2//:lib",
+    ],
+)
+
diff --git a/tools/distrib/bazel/tests-ceftests-mac-BUILD.bazel b/tools/distrib/bazel/tests-ceftests-mac-BUILD.bazel
new file mode 100644
index 000000000..0d0df9ec3
--- /dev/null
+++ b/tools/distrib/bazel/tests-ceftests-mac-BUILD.bazel
@@ -0,0 +1,53 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "ceftests"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel/mac:app_helpers.bzl", "declare_all_helper_apps", "declare_main_app")
+
+#
+# MacOS app bundle target.
+#
+
+filegroup(
+    name = "ResourcesMac",
+    srcs = [
+        "English.lproj/InfoPlist.strings",
+        "English.lproj/MainMenu.xib",
+        "{}.icns".format(PRODUCT_NAME),
+    ],
+)
+
+# Helper app bundles.
+declare_all_helper_apps(
+    name = PRODUCT_NAME,
+    info_plist = "helper-Info.plist.in",
+    deps = [
+        "{}:RendererLibMac".format(PKG_NAME),
+    ],
+)
+
+# Main app bundle.
+declare_main_app(
+    name = PRODUCT_NAME,
+    info_plist = "Info.plist.in",
+    deps = [
+        "{}:BrowserLibMac".format(PKG_NAME),
+    ],
+    linkopts = [
+        "-framework IOSurface",
+        "-framework OpenGL",
+    ],
+    resources = [
+        ":ResourcesMac",
+        "{}:ResourcesMac".format(PKG_NAME),
+        "//tests/shared:Resources",
+    ]
+)
diff --git a/tools/distrib/bazel/tests-ceftests-win-BUILD.bazel b/tools/distrib/bazel/tests-ceftests-win-BUILD.bazel
new file mode 100644
index 000000000..14397898d
--- /dev/null
+++ b/tools/distrib/bazel/tests-ceftests-win-BUILD.bazel
@@ -0,0 +1,59 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+PRODUCT_NAME = "ceftests"
+PKG_NAME = "//tests/{}".format(PRODUCT_NAME)
+
+# Allow access from the parent package only.
+package(default_visibility = [
+    "{}:__pkg__".format(PKG_NAME),
+])
+
+load("//bazel:copy_filegroups.bzl", "copy_filegroups")
+load("//bazel/win:exe_helpers.bzl", "declare_exe")
+
+#
+# Windows executable target.
+#
+
+# Copy resources into the current project.
+copy_target = "{}_resources".format(PRODUCT_NAME)
+copy_filegroups(
+    name = copy_target,
+    filegroups = [
+        "{}:Resources".format(PKG_NAME),
+    ],
+    remove_prefixes = [
+        "tests/{}/resources".format(PRODUCT_NAME),
+    ],
+    add_prefix = "ceftests_files",
+)
+
+declare_exe(
+    name = PRODUCT_NAME,
+    srcs = [
+        "{}:SrcsWin".format(PKG_NAME),
+        "//tests/shared:SrcsWin",
+    ],
+    deps = [
+        "//tests/gtest",
+    ],
+    rc_file = "{}.rc".format(PRODUCT_NAME),
+    manifest_srcs = [
+        "compatibility.manifest",
+        "{}.exe.manifest".format(PRODUCT_NAME),
+    ],
+    resources_srcs = [
+        "{}:ResourceH".format(PKG_NAME),
+        "{}.ico".format(PRODUCT_NAME),
+        "small.ico",
+        "//tests/shared:Resources",
+    ],
+    linkopts = [
+        "/SUBSYSTEM:CONSOLE",
+    ],
+    data = [
+        ":{}".format(copy_target),
+    ],
+)
diff --git a/tools/distrib/bazel/tests-gtest-BUILD.bazel b/tools/distrib/bazel/tests-gtest-BUILD.bazel
new file mode 100644
index 000000000..1c6539b64
--- /dev/null
+++ b/tools/distrib/bazel/tests-gtest-BUILD.bazel
@@ -0,0 +1,71 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("//bazel/win:variables.bzl",
+     WIN_COMMON_LINKOPTS="COMMON_LINKOPTS",
+     WIN_COMMON_COPTS="COMMON_COPTS",
+     WIN_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     WIN_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG")
+load("//bazel/linux:variables.bzl",
+     LINUX_COMMON_LINKOPTS="COMMON_LINKOPTS",
+     LINUX_COMMON_COPTS="COMMON_COPTS",
+     LINUX_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     LINUX_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG")
+load("//bazel/mac:variables.bzl",
+     MAC_COMMON_LINKOPTS="COMMON_LINKOPTS",
+     MAC_COMMON_COPTS="COMMON_COPTS",
+     MAC_COMMON_COPTS_RELEASE="COMMON_COPTS_RELEASE",
+     MAC_COMMON_COPTS_DEBUG="COMMON_COPTS_DEBUG")
+load("@rules_cc//cc:defs.bzl", "cc_library", "objc_library")
+
+# Allow access from targets in other packages.
+package(default_visibility = [
+    "//visibility:public",
+])
+
+cc_library(
+    name = "gtest",
+    srcs = [
+        "include/gtest/gtest.h",
+        "src/gtest-all.cc",
+        "teamcity/include/teamcity_gtest.h",
+        "teamcity/src/teamcity_gtest.cpp",
+        "teamcity/src/teamcity_messages.cpp",
+        "teamcity/src/teamcity_messages.h",
+    ],
+    local_defines = [
+        # In order to allow regex matches in gtest to be shared between Windows
+        # and other systems we tell gtest to always use it's internal engine.
+        "GTEST_HAS_POSIX_RE=0",
+        "GTEST_LANG_CXX11=1",
+    ],
+    defines = [
+        # All dependent targets are unit tests.
+        "UNIT_TEST",
+    ],
+    includes = [
+        # The gtest-all.cc file uses #include "gtest/gtest.h"
+        "include",
+    ],
+    copts = select({
+        "@platforms//os:windows": [
+            # Disable unused variable warning.
+            "/wd4800",
+        ] + WIN_COMMON_COPTS,
+        "@platforms//os:linux": LINUX_COMMON_COPTS,
+        "@platforms//os:macos":  MAC_COMMON_COPTS,
+        "//conditions:default": None,
+    }) + select({
+        "//:windows_opt": WIN_COMMON_COPTS_RELEASE,
+        "//:windows_dbg": WIN_COMMON_COPTS_DEBUG,
+        "//:windows_fastbuild": WIN_COMMON_COPTS_DEBUG,
+        "//:linux_opt": LINUX_COMMON_COPTS_RELEASE,
+        "//:linux_dbg": LINUX_COMMON_COPTS_DEBUG,
+        "//:linux_fastbuild": LINUX_COMMON_COPTS_DEBUG,
+        "//:macos_opt": MAC_COMMON_COPTS_RELEASE,
+        "//:macos_dbg": MAC_COMMON_COPTS_DEBUG,
+        "//:macos_fastbuild": MAC_COMMON_COPTS_DEBUG,
+        "//conditions:default": None,
+    }),
+)
diff --git a/tools/distrib/bazel/tests-shared-BUILD.bazel.in b/tools/distrib/bazel/tests-shared-BUILD.bazel.in
new file mode 100644
index 000000000..09ab38559
--- /dev/null
+++ b/tools/distrib/bazel/tests-shared-BUILD.bazel.in
@@ -0,0 +1,144 @@
+# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
+# reserved. Use of this source code is governed by a BSD-style license that
+# can be found in the LICENSE file.
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "objc_library")
+
+# Allow access from all //tests packages.
+package(default_visibility = [
+    "//tests:__subpackages__",
+])
+
+#
+# Source file lists.
+#
+
+srcs_common = [
+    ${shared_sources_common}
+]
+
+srcs_browser = [
+    ${shared_sources_browser}
+]
+
+srcs_browser_linux = [
+    ${shared_sources_linux}
+]
+
+srcs_browser_mac = [
+    ${shared_sources_mac}
+]
+
+srcs_browser_mac_ceftests = [
+    ${ceftests_sources_mac_browser_shared}
+]
+
+srcs_browser_win = [
+    ${shared_sources_win}
+]
+
+srcs_renderer = [
+    ${shared_sources_renderer}
+]
+
+srcs_renderer_mac = [
+    ${shared_sources_mac_helper}
+]
+
+srcs_renderer_mac_ceftests = [
+    ${ceftests_sources_mac_helper_shared}
+]
+
+srcs_resources = [
+    ${shared_sources_resources}
+]
+
+filegroup(
+    name = "Resources",
+    srcs = srcs_resources,
+)
+
+#
+# MacOS targets.
+#
+
+objc_library(
+    name = "BrowserLibMac",
+    srcs = srcs_common + srcs_browser + srcs_browser_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+    ],
+)
+
+cc_library(
+    name = "RendererLibMac",
+    srcs = srcs_common + srcs_renderer + srcs_renderer_mac,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+    ],
+)
+
+# Same as above, but adding additional files for ceftests. This is a workaround
+# for ceftests including browser and renderer test code in the same cc files.
+# Needs to be defined here because Bazel disallows direct access to files
+# outside of the package directory.
+
+objc_library(
+    name = "BrowserLibMacCefTests",
+    srcs = srcs_browser_mac_ceftests,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        ":BrowserLibMac",
+    ],
+)
+
+objc_library(
+    name = "RendererLibMacCefTests",
+    srcs = srcs_renderer_mac_ceftests,
+    target_compatible_with = [
+        "@platforms//os:macos",
+    ],
+    deps = [
+        "//:cef_wrapper",
+        ":RendererLibMac",
+    ],
+)
+
+#
+# Windows targets.
+#
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration and avoids issues with Windows discarding symbols (like WinMain)
+# when linking libraries.
+filegroup(
+    name = "SrcsWin",
+    srcs = srcs_common + srcs_browser + srcs_browser_win + srcs_renderer,
+    target_compatible_with = [
+        "@platforms//os:windows",
+    ],
+)
+
+#
+# Linux targets.
+#
+
+# Include files directly in the declare_exe target. This simplifies the build
+# configuration.
+filegroup(
+    name = "SrcsLinux",
+    srcs = srcs_common + srcs_browser + srcs_browser_linux + srcs_renderer,
+    target_compatible_with = [
+        "@platforms//os:linux",
+    ],
+)
+
diff --git a/tools/distrib/linux/README.minimal.txt b/tools/distrib/linux/README.minimal.txt
index 16cf3cb29..deb1b9da7 100644
--- a/tools/distrib/linux/README.minimal.txt
+++ b/tools/distrib/linux/README.minimal.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 include     Contains all required CEF header files.
@@ -23,6 +25,11 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/linux/README.standard.txt b/tools/distrib/linux/README.standard.txt
index 24c48dab8..1bda13b38 100644
--- a/tools/distrib/linux/README.standard.txt
+++ b/tools/distrib/linux/README.standard.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 Debug       Contains libcef.so and other components required to run the debug
@@ -47,6 +49,28 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
+  To build the bundled cefclient sample application using Bazel:
+
+  1. Install Bazelisk [https://github.com/bazelbuild/bazelisk/blob/master/README.md]
+  2. Install the patchelf package:
+     $ sudo apt install patchelf
+  3. Build using Bazel:
+     $ bazel build //tests/cefclient
+  4. Run using Bazel:
+     $ bazel run //tests/cefclient
+
+  Other sample applications (cefsimple, ceftests) can be built in the same way.
+
+  Additional notes:
+  - To generate a Debug build add `-c dbg` (both `build` and `run`
+    command-line).
+  - To pass arguments using the `run` command add `-- [...]` at the end.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/mac/README.minimal.txt b/tools/distrib/mac/README.minimal.txt
index 723edbdf5..3bbbcd326 100644
--- a/tools/distrib/mac/README.minimal.txt
+++ b/tools/distrib/mac/README.minimal.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 include     Contains all required CEF header files.
@@ -20,6 +22,11 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/mac/README.redistrib.txt b/tools/distrib/mac/README.redistrib.txt
index a66f27cb8..33b6eeac5 100644
--- a/tools/distrib/mac/README.redistrib.txt
+++ b/tools/distrib/mac/README.redistrib.txt
@@ -6,7 +6,7 @@ the "required" section must be redistributed with all applications using CEF.
 Components listed under the "optional" section may be excluded if the related
 features will not be used.
 
-Applications using CEF on OS X must follow a specific app bundle structure.
+Applications using CEF on MacOS must follow a specific app bundle structure.
 Replace "cefclient" in the below example with your application name.
 
 cefclient.app/
diff --git a/tools/distrib/mac/README.standard.txt b/tools/distrib/mac/README.standard.txt
index 4de70c205..cf0a5d284 100644
--- a/tools/distrib/mac/README.standard.txt
+++ b/tools/distrib/mac/README.standard.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 Debug       Contains the "Chromium Embedded Framework.framework" and other
@@ -41,6 +43,30 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
+  To build the bundled cefclient sample application using Bazel:
+
+  1. Install Bazelisk [https://github.com/bazelbuild/bazelisk/blob/master/README.md]
+  2. Build using Bazel:
+     $ bazel build //tests/cefclient
+  3. Run using Bazel:
+     $ bazel run //tests/cefclient
+
+  Other sample applications (cefsimple, ceftests) can be built in the same way.
+
+  Additional notes:
+  - To generate a Debug build add `-c dbg` (both `build` and `run`
+    command-line).
+  - To generate an Intel 64-bit cross-compile build on an ARM64 host add
+    `--cpu=darwin_x86_64` (both `build` and `run` command-line).
+  - To generate an ARM64 cross-compile build on an Intel 64-bit host add
+    `--cpu=darwin_arm64` (both `build` and `run` command-line).
+  - To pass arguments using the `run` command add `-- [...]` at the end.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/win/README.minimal.txt b/tools/distrib/win/README.minimal.txt
index 4d8207efa..ab07bdd89 100644
--- a/tools/distrib/win/README.minimal.txt
+++ b/tools/distrib/win/README.minimal.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 include     Contains all required CEF header files.
@@ -24,6 +26,11 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/win/README.standard.txt b/tools/distrib/win/README.standard.txt
index 92d77972d..9209b6cdf 100644
--- a/tools/distrib/win/README.standard.txt
+++ b/tools/distrib/win/README.standard.txt
@@ -1,6 +1,8 @@
 CONTENTS
 --------
 
+bazel       Contains Bazel configuration files shared by all targets.
+
 cmake       Contains CMake configuration files shared by all targets.
 
 Debug       Contains libcef.dll, libcef.lib and other components required to
@@ -47,6 +49,28 @@ Building using CMake:
   CMake can be used to generate project files in many different formats. See
   usage instructions at the top of the CMakeLists.txt file.
 
+Building using Bazel:
+  Bazel can be used to build CEF-based applications. CEF support for Bazel is
+  considered experimental. For current development status see
+  https://github.com/chromiumembedded/cef/issues/3757.
+
+  To build the bundled cefclient sample application using Bazel:
+
+  1. Install Bazelisk [https://github.com/bazelbuild/bazelisk/blob/master/README.md]
+  2. Build using Bazel:
+     $ bazel build //tests/cefclient
+  3. Run using Bazel:
+     $ bazel run //tests/cefclient/win:cefclient.exe
+
+  Other sample applications (cefsimple, ceftests) can be built in the same way.
+
+  Additional notes:
+  - To generate a Debug build add `-c dbg` (both `build` and `run`
+    command-line).
+  - To pass arguments using the `run` command add `-- [...]` at the end.
+  - Windows x86 and ARM64 builds using Bazel may be broken, see
+    https://github.com/bazelbuild/bazel/issues/22164.
+
 Please visit the CEF Website for additional usage information.
 
 https://bitbucket.org/chromiumembedded/cef/
diff --git a/tools/distrib/win/transfer_standard.cfg b/tools/distrib/win/transfer_standard.cfg
index cb06d2fd3..4c44bb098 100644
--- a/tools/distrib/win/transfer_standard.cfg
+++ b/tools/distrib/win/transfer_standard.cfg
@@ -12,14 +12,14 @@
 [
   {
     'source' : '../build/win/compatibility.manifest',
-    'target' : 'tests/cefclient/resources/win/compatibility.manifest',
+    'target' : 'tests/cefclient/win/compatibility.manifest',
   },
   {
     'source' : '../build/win/compatibility.manifest',
-    'target' : 'tests/cefsimple/compatibility.manifest',
+    'target' : 'tests/cefsimple/win/compatibility.manifest',
   },
   {
     'source' : '../build/win/compatibility.manifest',
-    'target' : 'tests/ceftests/resources/win/compatibility.manifest',
+    'target' : 'tests/ceftests/win/compatibility.manifest',
   },
 ]
diff --git a/tools/make_distrib.py b/tools/make_distrib.py
index 3507848de..7b68fa41d 100644
--- a/tools/make_distrib.py
+++ b/tools/make_distrib.py
@@ -4,6 +4,7 @@
 
 from __future__ import absolute_import
 from __future__ import print_function
+from bazel_util import bazel_substitute, bazel_last_error, bazel_set_quiet
 from cef_version import VersionFormatter
 from date_util import *
 from exec_util import exec_cmd
@@ -357,6 +358,64 @@ def transfer_tools_files(script_dir, build_dirs, output_dir):
   copy_files_list(os.path.join(script_dir, 'distrib', 'tools'), output_dir, files)
 
 
+def copy_bazel_file_with_substitution(path, target_path, variables, relative_path):
+  data = read_file(path)
+  bazel_set_quiet(True)
+  result = bazel_substitute(data, variables, path_relative_to=relative_path, label=path)
+  last_error = bazel_last_error()
+  bazel_set_quiet(False)
+  if not last_error is None:
+    raise Exception(last_error)
+  if not options.quiet:
+    sys.stdout.write('Writing %s file.\n' % target_path)
+  write_file(target_path, result)
+
+
+def transfer_bazel_files(bazel_dir, output_dir, variables, require_parent_dir):
+  # All input files.
+  bazel_files = get_files(os.path.join(bazel_dir, '*')) + get_files(os.path.join(bazel_dir, '.*'))
+
+  # Map of path component to required platform.
+  platform_map = {
+    'linux': 'linux',
+    'mac': 'mac',
+    'win': 'windows',
+  }
+
+  for path in bazel_files:
+    name = os.path.split(path)[1]
+
+    # |name| uses hyphens to indicate directory components.
+    directory_parts = name.split('-')[:-1]
+
+    # Skip files that don't apply for the current platform.
+    skip = False
+    for part in directory_parts:
+      if part in platform_map and platform_map[part] != platform:
+        skip = True
+        break
+    if skip:
+      sys.stdout.write('Skipping %s file.\n' % path)
+      continue
+
+    target_path = os.path.join(output_dir, name.replace('-', '/'))
+    target_dir = os.path.split(target_path)[0]
+    if not os.path.isdir(target_dir):
+      parent_dir = os.path.split(target_dir)[0]
+      if not os.path.isdir(parent_dir) and require_parent_dir:
+        # Don't write tests/* files if the tests/ directory is missing.
+        sys.stdout.write('Skipping %s file.\n' % path)
+        continue
+      make_dir(target_dir)
+    if target_path.endswith('.in'):
+      # Copy with variable substitution.
+      relative_path = '/'.join(directory_parts)
+      copy_bazel_file_with_substitution(path, target_path[:-3], variables, relative_path)
+    else:
+      # Copy as-is.
+      copy_file(path, target_path, options.quiet)
+
+
 def normalize_headers(file, new_path=''):
   """ Normalize headers post-processing. Remove the path component from any
       project include directives. """
@@ -623,6 +682,12 @@ parser.add_option(
     dest='noarchive',
     default=False,
     help='don\'t create archives for output directories')
+parser.add_option(
+    '--no-sandbox',
+    action='store_true',
+    dest='nosandbox',
+    default=False,
+    help='don\'t create cef_sandbox files')
 parser.add_option(
     '--ninja-build',
     action='store_true',
@@ -751,9 +816,9 @@ chromium_rev = git.get_hash(src_dir)
 date = get_date()
 
 # format version strings
-formatter = VersionFormatter()
-cef_ver = formatter.get_version_string()
-chromium_ver = formatter.get_chromium_version_string()
+version_formatter = VersionFormatter()
+cef_ver = version_formatter.get_version_string()
+chromium_ver = version_formatter.get_chromium_version_string()
 
 # list of output directories to be archived
 archive_dirs = []
@@ -1084,7 +1149,7 @@ elif platform == 'windows':
 
   # Generate the cef_sandbox.lib merged library. A separate *_sandbox build
   # should exist when GN is_official_build=true.
-  if mode in ('standard', 'minimal', 'sandbox'):
+  if mode in ('standard', 'minimal', 'sandbox') and not options.nosandbox:
     dirs = {
         'Debug': (build_dir_debug + '_sandbox', build_dir_debug),
         'Release': (build_dir_release + '_sandbox', build_dir_release)
@@ -1162,21 +1227,21 @@ elif platform == 'windows':
                         'tests/shared/', shared_dir, options.quiet)
 
     # transfer cefclient files
-    transfer_gypi_files(cef_dir, cef_paths2['cefclient_sources_win'], \
-                        'tests/cefclient/', cefclient_dir, options.quiet)
-    transfer_gypi_files(cef_dir, cef_paths2['cefclient_sources_resources_win'], \
+    transfer_gypi_files(cef_dir, cef_paths2['cefclient_sources_win'] +
+                        cef_paths2['cefclient_sources_resources_win'] +
+                        cef_paths2['cefclient_sources_resources_win_rc'],
                         'tests/cefclient/', cefclient_dir, options.quiet)
 
     # transfer cefsimple files
-    transfer_gypi_files(cef_dir, cef_paths2['cefsimple_sources_win'], \
-                        'tests/cefsimple/', cefsimple_dir, options.quiet)
-    transfer_gypi_files(cef_dir, cef_paths2['cefsimple_sources_resources_win'], \
+    transfer_gypi_files(cef_dir, cef_paths2['cefsimple_sources_win'] +
+                        cef_paths2['cefsimple_sources_resources_win'] +
+                        cef_paths2['cefsimple_sources_resources_win_rc'],
                         'tests/cefsimple/', cefsimple_dir, options.quiet)
 
     # transfer ceftests files
-    transfer_gypi_files(cef_dir, cef_paths2['ceftests_sources_win'], \
-                        'tests/ceftests/', ceftests_dir, options.quiet)
-    transfer_gypi_files(cef_dir, cef_paths2['ceftests_sources_resources_win'], \
+    transfer_gypi_files(cef_dir, cef_paths2['ceftests_sources_win'] +
+                        cef_paths2['ceftests_sources_resources_win'] +
+                        cef_paths2['ceftests_sources_resources_win_rc'],
                         'tests/ceftests/', ceftests_dir, options.quiet)
 
 elif platform == 'mac':
@@ -1201,7 +1266,7 @@ elif platform == 'mac':
 
   # Generate the cef_sandbox.a merged library. A separate *_sandbox build
   # should exist when GN is_official_build=true.
-  if mode in ('standard', 'minimal', 'sandbox'):
+  if mode in ('standard', 'minimal', 'sandbox') and not options.nosandbox:
     dirs = {
         'Debug': (build_dir_debug + '_sandbox', build_dir_debug),
         'Release': (build_dir_release + '_sandbox', build_dir_release)
@@ -1314,9 +1379,9 @@ elif platform == 'mac':
     transfer_gypi_files(cef_dir, cef_paths2['cefclient_sources_mac'], \
                         'tests/cefclient/', cefclient_dir, options.quiet)
 
-    # transfer cefclient/resources/mac files
-    copy_dir(os.path.join(cef_dir, 'tests/cefclient/resources/mac'), \
-             os.path.join(cefclient_dir, 'resources/mac'), \
+    # transfer cefclient/mac files
+    copy_dir(os.path.join(cef_dir, 'tests/cefclient/mac'), \
+             os.path.join(cefclient_dir, 'mac'), \
              options.quiet)
 
     # transfer cefsimple files
@@ -1336,9 +1401,9 @@ elif platform == 'mac':
     transfer_gypi_files(cef_dir, cef_paths2['ceftests_sources_mac_helper'], \
                         'tests/ceftests/', ceftests_dir, options.quiet)
 
-    # transfer ceftests/resources/mac files
-    copy_dir(os.path.join(cef_dir, 'tests/ceftests/resources/mac'), \
-             os.path.join(ceftests_dir, 'resources/mac'), \
+    # transfer ceftests/mac files
+    copy_dir(os.path.join(cef_dir, 'tests/ceftests/mac'), \
+             os.path.join(ceftests_dir, 'mac'), \
              options.quiet)
 
 elif platform == 'linux':
@@ -1433,6 +1498,24 @@ elif platform == 'linux':
     transfer_gypi_files(cef_dir, cef_paths2['ceftests_sources_linux'], \
                         'tests/ceftests/', ceftests_dir, options.quiet)
 
+if mode == 'standard' or mode == 'minimal':
+  variables = {
+      'version_long': version_formatter.get_version_string(),
+      'version_short': version_formatter.get_short_version_string(),
+      'version_plist': version_formatter.get_plist_version_string(),
+  }
+  variables.update(cef_paths2)
+
+  copy_dir(
+      os.path.join(cef_dir, 'bazel'),
+      os.path.join(output_dir, 'bazel'), options.quiet)
+
+  transfer_bazel_files(
+      os.path.join(script_dir, 'distrib', 'bazel'),
+      output_dir,
+      variables,
+      require_parent_dir=(mode != 'standard'))
+
 if not options.noarchive:
   # create an archive for each output directory
   archive_format = os.getenv('CEF_ARCHIVE_FORMAT', 'zip')