Add end_message to print status messages on successfull exit.
diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim
index 85acf43..fedf6eb 100644
--- a/data/syntax-highlighting/vim/syntax/meson.vim
+++ b/data/syntax-highlighting/vim/syntax/meson.vim
@@ -80,6 +80,7 @@
   \ declare_dependency
   \ dependency
   \ disabler
+  \ end_message
   \ environment
   \ error
   \ executable
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index 5318364..714a259 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -431,6 +431,16 @@
 
 Returns a [disabler object](#disabler-object). Added in 0.44.0.
 
+### end_message()
+
+Added in 0.50.0. This function can be used to print status messages at
+the end of a Meson run. It is usually used to print a summary of
+essential configuration settings. The function takes either one or two
+strings, so it can be used to print both plain messages as well as
+key-value pairs. Meson will automatically format the latter kind so
+that all values are vertically aligned. Thus it recommended to use
+fairly short message strings.
+
 ### error()
 
 ``` meson
diff --git a/docs/markdown/snippets/end_messages.md b/docs/markdown/snippets/end_messages.md
new file mode 100644
index 0000000..0b04c67
--- /dev/null
+++ b/docs/markdown/snippets/end_messages.md
@@ -0,0 +1,20 @@
+## New end_messages method
+
+The new `end_messages` function allows you to create status messages
+that will be printed at the end of the Meson run rather than
+immediately. For example the following setup:
+
+```meson
+end_message('A short name:', 'value #1')
+end_message('A bit longer name:', 'value #2')
+```
+
+Will cause the following text to be printed as the last thing just
+before Meson exits.
+
+```
+Main project
+
+A short name:       value #1
+A bit longer name:  value #2
+```
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 1fcbc04..29d4e16 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -141,6 +141,7 @@
         self.test_setup_default_name = None
         self.find_overrides = {}
         self.searched_programs = set() # The list of all programs that have been searched for.
+        self.end_messages = {}
 
     def copy(self):
         other = Build(self.environment)
@@ -220,6 +221,12 @@
 
         return link_args.get(compiler.get_language(), [])
 
+    def add_end_message(self, subproject, message):
+        assert(isinstance(message, list))
+        if subproject not in self.end_messages:
+            self.end_messages[subproject] = []
+        self.end_messages[subproject].append(message)
+
 class IncludeDirs:
     def __init__(self, curdir, dirs, is_system, extra_build_dirs=None):
         self.curdir = curdir
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 4f09c0f..822d844 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -1982,6 +1982,7 @@
                            'declare_dependency': self.func_declare_dependency,
                            'dependency': self.func_dependency,
                            'disabler': self.func_disabler,
+                           'end_message': self.func_end_message,
                            'environment': self.func_environment,
                            'error': self.func_error,
                            'executable': self.func_executable,
@@ -2606,6 +2607,17 @@
         argstr = self.get_message_string_arg(node)
         mlog.log(mlog.bold('Message:'), argstr)
 
+    @stringArgs
+    @noKwargs
+    @FeatureNew('end_message', '0.50.0')
+    def func_end_message(self, node, args, kwargs):
+        if len(args) == 0 or len(args) > 2:
+            raise InvalidArguments('End_message takes exacly one or two arguments.')
+        if '\n' in ' '.join(args):
+            raise InvalidArguments('The argument strings may not contain linefeeds.')
+        self.build.add_end_message(self.subproject, args)
+        pass
+
     @FeatureNew('warning', '0.44.0')
     @noKwargs
     def func_warning(self, node, args, kwargs):
diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py
index f9a5e1c..c594a39 100644
--- a/mesonbuild/msetup.py
+++ b/mesonbuild/msetup.py
@@ -139,6 +139,28 @@
                          (env.coredata.pkgconf_envvar, curvar))
             env.coredata.pkgconf_envvar = curvar
 
+    def print_messagelist(self, project_name, messages):
+        max_length = max([len(x[0]) for x in messages])
+        mlog.log(mlog.bold('\n' + project_name + '\n'))
+        for m in messages:
+            if len(m) == 1:
+                mlog.log(m[0])
+            else:
+                padding = ' '*(max_length - len(m[0]) + 2)
+                mlog.log(m[0] + padding, mlog.bold(m[1]))
+
+    def print_end_messages(self, end_messages):
+        if len(end_messages) == 0:
+            return
+        mlog.log(mlog.bold('\nProject status messages'))
+        all_projects = end_messages.keys()
+        for p in sorted(all_projects):
+            if p == '':
+                continue
+            self.print_messagelist(p, end_messages[p])
+        if '' in end_messages:
+            self.print_messagelist('Main project', end_messages[''])
+
     def generate(self):
         env = environment.Environment(self.source_dir, self.build_dir, self.options)
         mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
@@ -202,6 +224,7 @@
                 coredata.write_cmd_line_file(self.build_dir, self.options)
             else:
                 coredata.update_cmd_line_file(self.build_dir, self.options)
+            self.print_end_messages(b.end_messages)
         except:
             if 'cdf' in locals():
                 old_cdf = cdf + '.prev'
diff --git a/run_project_tests.py b/run_project_tests.py
index 0d64f47..0245148 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -697,6 +697,8 @@
         if '.dub' in root: # external deps are here
             continue
         for fname in files:
+            if fname.startswith('.#') or fname.startswith('#'): # Emacs temp files.
+                continue
             if os.path.splitext(fname)[1].lower() in check_suffixes:
                 bn = os.path.basename(fname)
                 if bn == 'sitemap.txt' or bn == 'meson-test-run.txt':
diff --git a/test cases/common/209 end message/meson.build b/test cases/common/209 end message/meson.build
new file mode 100644
index 0000000..c185d42
--- /dev/null
+++ b/test cases/common/209 end message/meson.build
@@ -0,0 +1,6 @@
+project('end message', 'c')
+
+end_message('Aligned end message of main project:', '#1')
+subproject('bob') # Intentionally between calls to end_message. Just in case.
+end_message('End message of main project.')
+end_message('Aligned end message of main project:', '#2')
diff --git a/test cases/common/209 end message/subprojects/bob/meson.build b/test cases/common/209 end message/subprojects/bob/meson.build
new file mode 100644
index 0000000..26269b9
--- /dev/null
+++ b/test cases/common/209 end message/subprojects/bob/meson.build
@@ -0,0 +1,5 @@
+project('bob', 'c')
+
+end_message('Aligned end message of Bob:', '#1')
+end_message('End message of Bob.')
+end_message('Aligned end message of Bob:', '#2')