diff --git a/meta/lib/oeqa/selftest/cases/bblayers.py b/meta/lib/oeqa/selftest/cases/bblayers.py
index c6bd5a1f6a..1dd3046aa6 100644
--- a/meta/lib/oeqa/selftest/cases/bblayers.py
+++ b/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -122,6 +122,18 @@ class BitbakeLayers(OESelftestTestCase):
             fullpath = os.path.join(layerpath, "conf", "templates", "buildconf-1", f)
             self.assertTrue(os.path.exists(fullpath), "Template configuration file {} not found".format(fullpath))
 
+        cmd = 'oe-setup-build --topdir {}'.format(layerpath)
+        result = runCmd(cmd)
+        cond = "scripts/oe-setup-build -c " in result.output and "test-bitbakelayer-layercreate/conf/templates/buildconf-1" in result.output
+        self.assertTrue(cond, "Incorrect output from {}: {}".format(cmd, result.output))
+
+        # rather than hardcode the build setup cmdline here, let's actually run what the tool suggests to the user
+        cmd = None
+        for l in result.output.splitlines():
+            if "scripts/oe-setup-build -c " in l:
+                cmd = l + " --no-shell"
+        result = runCmd(cmd)
+
     def get_recipe_basename(self, recipe):
         recipe_file = ""
         result = runCmd("bitbake-layers show-recipes -f %s" % recipe)
diff --git a/scripts/oe-setup-build b/scripts/oe-setup-build
new file mode 100755
index 0000000000..56bbd24b26
--- /dev/null
+++ b/scripts/oe-setup-build
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+import argparse
+import json
+import os
+import subprocess
+
+def makebuildpath(topdir, templatelocation, template):
+    return os.path.join(topdir, "build-{}-{}".format(os.path.basename(os.path.abspath(os.path.join(templatelocation,'..','..'))), template))
+
+def print_templates(topdir, verbose):
+    print("Available build configuration templates (re-run with -v to see their descriptions):\n")
+    for (dir, dirs, files) in os.walk(topdir):
+        if dir.endswith('conf/templates'):
+            for d in dirs:
+                print("{} -c {}\nwill create a build in {}\n".format(__file__, os.path.join(dir, d), makebuildpath(args.topdir, dir, d)))
+                if verbose:
+                    if os.path.join(dir, d).endswith('meta-poky/conf/templates/default'):
+                        print("Description: this is the reference configuration of the poky reference distribution (choose this if you are uncertain).")
+                    elif os.path.join(dir, d).endswith('meta/conf/templates/default'):
+                        print("Description: this is the reference configuration of the openembedded-core layer (choose this if you are uncertain).")
+                    else:
+                        print("Description:", open(os.path.join(dir, d, 'conf-notes.txt')).read())
+                    print("---")
+
+        # Do not recurse into build directories; they can be enormous
+        if 'conf' in dirs and 'bblayers.conf' in os.listdir(os.path.join(dir, 'conf')):
+            dirs.clear()
+        # Do not recurse into sstate-cache or downloads similarly
+        if 'universal' in dirs and '00' in dirs:
+            dirs.clear()
+        if 'uninative' in dirs and 'git2' in dirs:
+            dirs.clear()
+
+def setup_build(defaulttop, template, builddir, no_shell):
+    if not builddir:
+        builddir = makebuildpath(defaulttop, os.path.dirname(template), os.path.basename(template))
+    coredir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+    cmd = "TEMPLATECONF={} . {} {}".format(template, os.path.join(coredir, 'oe-init-build-env'), builddir)
+    if not no_shell:
+        cmd = cmd + " && {}".format(os.environ['SHELL'])
+    print("Running:", cmd)
+    subprocess.run(cmd, shell=True, executable=os.environ['SHELL'])
+
+parser = argparse.ArgumentParser(description="A script that discovers available build configuration templates and sets up a build environment based on one of them")
+
+defaulttop = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
+parser.add_argument("--topdir", default=defaulttop, help='Where to look for available build configuration templates (default is {}).'.format(defaulttop))
+
+parser.add_argument('-v', action='store_true',
+        help='Print a description for each available build configuration template.')
+
+parser.add_argument('-c', metavar='template_path', help="Use build configuration template in template_path to set up a build (run this script without arguments or with -v to see what is available)")
+parser.add_argument('-b', metavar='build_path', help="Set up a build in build_path (run this script without arguments or with -v to see where it would be by default)")
+parser.add_argument('--no-shell', action='store_true',
+        help='Create a build but do not start a shell session with it.')
+
+args = parser.parse_args()
+
+if not args.c:
+    print_templates(args.topdir, args.v)
+else:
+    setup_build(defaulttop, args.c, args.b, args.no_shell)
diff --git a/scripts/oe-setup-layers b/scripts/oe-setup-layers
index 6ecaffed75..8336037630 100755
--- a/scripts/oe-setup-layers
+++ b/scripts/oe-setup-layers
@@ -17,6 +17,7 @@ import os
 import subprocess
 
 def _do_checkout(args, json):
+    oesetupbuild = None
     layers = json['sources']
     for l_name in layers:
         l_data = layers[l_name]
@@ -53,6 +54,12 @@ def _do_checkout(args, json):
         print("Running '{}' in {}".format(cmd, layerdir))
         subprocess.check_output(cmd, shell=True, cwd=layerdir)
 
+        if os.path.exists(os.path.join(layerdir, 'scripts/oe-setup-build')):
+            oesetupbuild = os.path.join(layerdir, 'scripts/oe-setup-build')
+
+    if oesetupbuild:
+        print("\nRun {} to list available build configuration templates and set up a build from one of them".format(oesetupbuild))
+
 parser = argparse.ArgumentParser(description="A self contained python script that fetches all the needed layers and sets them to correct revisions using data in a json format from a separate file. The json data can be created from an active build directory with 'bitbake-layers create-layers-setup destdir' and there's a sample file and a schema in meta/files/")
 
 parser.add_argument('--force-bootstraplayer-checkout', action='store_true',
