Index: .gitignore
===================================================================
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
 /node_modules/
 /chromium-snapshots/
+/build/
 *.symbols
-/compiled/bindings.cpp.js
-/compiled/bindings.js
 /lib/compiled.js
Index: .hgignore
===================================================================
--- a/.hgignore
+++ b/.hgignore
@@ -1,8 +1,7 @@
 ^node_modules$
 ^chromium-snapshots$
+^build$
 
 syntax: glob
 *.symbols
-compiled/bindings.cpp.js
-compiled/bindings.js
-lib/compiled.js
+lib/compiled.js
\ No newline at end of file
Index: README.md
===================================================================
--- a/README.md
+++ b/README.md
@@ -14,32 +14,46 @@
 In order to improve performance and memory usage, some of the code (located
 inside the `compiled` directory) is written in C++ and compiled to JavaScript
 via Empscripten.
 
 ### Requirements
 
 * [Emscripten 1.37.3](https://github.com/kripken/emscripten)
 * [Python 2.7](https://www.python.org)
+* [meson 0.40.0+](https://www.mesonbuild.com)
+* [ninja](https://www.ninja-build.org)
+
+Make sure that meson and ninja are in your PATH.
 
 ### Running Emscripten
 
-After installing and configuring Emscripten you can run the following command:
+After installing and configuring Emscripten you can setup the build
+with the following commands:
+
+    meson build
 
-    python compile
+By default it will create a debug build. Pass `--buildtype release` to
+create a release build.
+
+Then to build just do:
+
+    ninja -C build
+
+This will regenerate the build files as needed.
 
 This will produce a `lib/compiled.js` exporting the classes defined in C++ code.
 
 ### Technical details
 
 Compilation is currently a two-step process. In the bindings generation step,
 the source files are compiled into `compiled/bindings.cpp.js` with the
 `PRINT_BINDINGS` symbol defined. This application is then executed via Node.js
 and will print JavaScript wrappers for the C++ classes to
-`compiled/bindings.js` according to definitions within the `EMSCRIPTEN_BINDINGS`
+`build/bindings.js` according to definitions within the `EMSCRIPTEN_BINDINGS`
 macro in `compiled/bindings.cpp`.
 
 In the actual compilation step the source files are compiled into
 `lib/compiled.js` without the `PRINT_BINDINGS` symbol, so that the
 `EMSCRIPTEN_BINDINGS` macro ignores its contents and merely emits some generic
 functions necessary for the JavaScript bindings to work. The previously
 generated `compiled/bindings.js` file is added to the end of Emscripten output.
 
Index: compile
===================================================================
deleted file mode 100755
--- a/compile
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python
-
-# This file is part of Adblock Plus <https://adblockplus.org/>,
-# Copyright (C) 2006-present eyeo GmbH
-#
-# Adblock Plus is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# Adblock Plus is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
-
-import argparse
-import os
-import subprocess
-import sys
-
-BASE_DIR = os.path.dirname(__file__)
-SOURCE_DIR = os.path.join(BASE_DIR, 'compiled')
-JS_LIBRARY = os.path.join(SOURCE_DIR, 'library.js')
-BINDINGS_GENERATOR = os.path.join(SOURCE_DIR, 'bindings.cpp.js')
-BINDINGS_OUTPUT = os.path.join(SOURCE_DIR, 'bindings.js')
-COMPILER_OUTPUT = os.path.join(BASE_DIR, 'lib', 'compiled.js')
-GENERATION_PARAMS = {
-    'SHELL_FILE': "'{}'".format(os.path.abspath(os.path.join(SOURCE_DIR,
-                                                             'shell.js'))),
-    'ASM_JS': 2,  # "almost asm"
-    'TOTAL_MEMORY': 16*1024*1024,
-    'TOTAL_STACK': 1*1024*1024,
-    'ALLOW_MEMORY_GROWTH': 1,
-    'NO_EXIT_RUNTIME': 1,
-    'NO_DYNAMIC_EXECUTION': 1,
-    'NO_FILESYSTEM': 1,
-    'INVOKE_RUN': 0,
-    'TEXTDECODER': 0,
-    'EXPORTED_RUNTIME_METHODS': ['cwrap', 'ccall', 'stringToAscii'],
-}
-DEFINES = []
-ADDITIONAL_PARAMS = ['-O3', '-m32', '-std=c++1z', '--memory-init-file', '0',
-                     '--emit-symbol-map', '-Wall', '-Werror', '-fno-exceptions',
-                     '-fno-rtti', '--js-library', JS_LIBRARY]
-
-
-def get_source_files(phase):
-    for (path, dirs, files) in os.walk(SOURCE_DIR):
-        for f in files:
-            if os.path.splitext(f)[1] != '.cpp':
-                continue
-            if (
-                phase != 'bindings' and
-                os.path.basename(path) == 'bindings' and
-                not f.startswith('runtime_')
-            ):
-                continue
-            yield os.path.join(path, f)
-
-def getenv(emscripten_config):
-    scope = {}
-    execfile(emscripten_config, scope, scope)
-    env = os.environ.copy()
-    env.update({
-        'EM_CONFIG': emscripten_config,
-        'EMSCRIPTEN': scope['EMSCRIPTEN_ROOT'],
-        'PYTHON': scope.get('PYTHON', sys.executable),
-        'NODE_JS': scope.get('NODE_JS', 'node'),
-    })
-    return env
-
-
-def generate_bindings(env):
-    params = [
-        env['PYTHON'], os.path.join(env['EMSCRIPTEN'], 'emcc'),
-        '-o', BINDINGS_GENERATOR, '-std=c++1z', '--js-library', JS_LIBRARY,
-        '--js-library', os.path.join(SOURCE_DIR, 'bindings', 'library.js'),
-    ] + list(get_source_files('bindings'))
-    subprocess.check_call(params, env=env)
-
-    with open(BINDINGS_OUTPUT, 'w') as file:
-        subprocess.check_call([env['NODE_JS'], BINDINGS_GENERATOR],
-                              stdout=file)
-
-
-def run_compiler(env, debug=False, tracing=False):
-    params = [
-        env['PYTHON'], os.path.join(env['EMSCRIPTEN'], 'emcc'),
-        '-o', COMPILER_OUTPUT,
-        '--post-js', BINDINGS_OUTPUT,
-    ]
-    # Order matters. We sort source files so that we always get the same
-    # results for the same source code.
-    params.extend(sorted(get_source_files('compile')))
-    params.extend('-D' + flag for flag in DEFINES)
-    for key, value in GENERATION_PARAMS.iteritems():
-        params.extend(['-s', '{}={}'.format(key, str(value))])
-    if debug:
-        params.append('-DDEBUG')
-        params.append('-g3')
-    if tracing:
-        params.append('--tracing')
-    params.extend(ADDITIONAL_PARAMS)
-    subprocess.check_call(params, env=env)
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(
-        description='Compile Emscripten-based C++ code to JavaScript'
-    )
-    parser.add_argument(
-        '--emscripten-config',
-        metavar='DIR',
-        default=os.path.expanduser('~/.emscripten'),
-        help='Emscripten installation directory'
-    )
-    parser.add_argument(
-        '-d', '--debug',
-        action='store_true',
-        help='Disable code minification'
-    )
-    parser.add_argument(
-        '-t', '--tracing',
-        action='store_true',
-        help='Enable memory tracing'
-    )
-    args = parser.parse_args()
-
-    env = getenv(args.emscripten_config)
-    generate_bindings(env)
-    run_compiler(env, debug=args.debug, tracing=args.tracing)
Index: meson.build
===================================================================
new file mode 100644
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,167 @@
+project('adblockpluscore', license: ['GPL3'], meson_version: '>0.40.0')
+
+# locate emscripten-config
+python = import('python3').find_python()
+emscripten_config = get_option('emscripten-config')
+command = run_command(python, '-c', 'import os.path, sys;print(os.path.expanduser(sys.argv[1]))', emscripten_config)
+if command.returncode() != 0
+  error(command.stderr().strip())
+endif
+emscripten_config = command.stdout().strip()
+message('Emscripten config file: ' + emscripten_config)
+
+# locate emcc
+command = run_command(python, '-c', 'import sys;exec(open(sys.argv[1]).read());print(EMSCRIPTEN_ROOT)', emscripten_config)
+if command.returncode() != 0
+  error(command.stderr().strip())
+endif
+emcc = join_paths(command.stdout().strip(), 'emcc')
+emcc = find_program(emcc)
+
+# locate node
+command = run_command(python, '-c', 'import sys;exec(open(sys.argv[1]).read());print(NODE_JS)', emscripten_config)
+if command.returncode() != 0
+  error(command.stderr().strip())
+endif
+nodejs = find_program(command.stdout().strip(), 'node', 'nodejs')
+
+JS_LIBRARY = files(join_paths('compiled', 'library.js'))
+BINDINGS_JS_LIBRARY = files(join_paths('compiled', 'bindings', 'library.js'))
+BINDINGS_GENERATOR = 'bindings.cpp.js'
+BINDINGS_OUTPUT = 'bindings.js'
+COMPILER_OUTPUT = 'compiled.js'
+# params for emcc compilation
+ADDITIONAL_PARAMS = [ '-O3', '-m32', '-std=c++1z', '--memory-init-file', '0',
+                      '--emit-symbol-map', '-Wall', '-Werror',
+                      '-fno-rtti' ]
+# additional params just for core
+CORE_PARAMS = [ '-fno-exceptions' ]
+
+DEFINES = []
+GENERATION_PARAMS = [
+  ['SHELL_FILE', '"' +
+   join_paths(meson.source_root(), 'compiled', 'shell.js') +
+   '"'],
+  ['ASM_JS', '2'],  # "almost asm"
+  ['TOTAL_MEMORY', 16*1024*1024],
+  ['TOTAL_STACK', 1*1024*1024],
+  ['ALLOW_MEMORY_GROWTH', 1],
+  ['NO_EXIT_RUNTIME', 1],
+  ['NO_DYNAMIC_EXECUTION', 1],
+  ['NO_FILESYSTEM', 1],
+  ['INVOKE_RUN', 0],
+  ['TEXTDECODER', 0],
+  ['EXPORTED_RUNTIME_METHODS', '["cwrap", "ccall", "stringToAscii"]']
+]
+
+
+shared_sources = [
+  'compiled/bindings/runtime_utils.cpp',
+  'compiled/filter/ActiveFilter.cpp',
+  'compiled/filter/BlockingFilter.cpp',
+  'compiled/filter/CommentFilter.cpp',
+  'compiled/filter/ElemHideBase.cpp',
+  'compiled/filter/ElemHideEmulationFilter.cpp',
+  'compiled/filter/ElemHideException.cpp',
+  'compiled/filter/ElemHideFilter.cpp',
+  'compiled/filter/Filter.cpp',
+  'compiled/filter/InvalidFilter.cpp',
+  'compiled/filter/RegExpFilter.cpp',
+  'compiled/filter/WhitelistFilter.cpp',
+  'compiled/storage/FilterStorage.cpp',
+  'compiled/subscription/DownloadableSubscription.cpp',
+  'compiled/subscription/Subscription.cpp',
+  'compiled/subscription/UserDefinedSubscription.cpp',
+]
+# sources specific to core
+core_sources = [
+  'compiled/traceInit.cpp',
+]
+# sources for the bindings generator
+bindings_sources = [
+  'compiled/bindings/generator.cpp',
+  'compiled/bindings/main.cpp',
+]
+
+defines_args = []
+foreach define : DEFINES
+  defines_args += '-D' + define
+endforeach
+
+generation_args = []
+foreach param : GENERATION_PARAMS
+  generation_args += '-s'
+  generation_args += param[0] + '=' + '@0@'.format(param[1])
+endforeach
+
+optional_args = []
+buildtype = get_option('buildtype')
+if buildtype.startswith('debug')
+  optional_args += '-DDEBUG'
+  optional_args += '-g3'
+endif
+tracing = get_option('tracing')
+if tracing
+  optional_args += '--tracing'
+endif
+
+compiler_args = defines_args + generation_args + ADDITIONAL_PARAMS
+
+# build objects.
+core_objects = []
+bindings_objects = []
+shared_objects = []
+foreach group : ['core', 'bindings', 'shared']
+  objects = []
+  foreach source_file : get_variable(group + '_sources')
+    output_file = source_file.underscorify() + '.o'
+    command = [ emcc, '-MD', '-MF', '@DEPFILE@', '-c', '-o', '@OUTPUT@',
+                '@INPUT@' ] + compiler_args
+    if group != 'bindings'
+      command += CORE_PARAMS + optional_args
+    endif
+    objects += custom_target(output_file,
+                             input: files(source_file),
+                             output: output_file,
+                             depfile: output_file + '.deps',
+                             command: command)
+  endforeach
+  # Ideally, we would call set_variable() here but that doesn't work with arrays
+  # (see https://github.com/mesonbuild/meson/issues/1481). So we have to do this
+  # shifting dance instead.
+  core_objects = bindings_objects
+  bindings_objects = shared_objects
+  shared_objects = objects
+endforeach
+
+# link the binding generator
+bindings_generator = custom_target(BINDINGS_GENERATOR,
+                                   input: bindings_objects + shared_objects,
+                                   output: BINDINGS_GENERATOR,
+                                   depend_files: [ JS_LIBRARY, BINDINGS_JS_LIBRARY ],
+                                   command: [
+                                     emcc, '-o', '@OUTPUT@',
+                                     '--js-library', JS_LIBRARY,
+                                     '--js-library', BINDINGS_JS_LIBRARY,
+                                     '@INPUT@'
+                                   ])
+# run the binding generator
+bindings_output = custom_target(BINDINGS_OUTPUT,
+                                input: bindings_generator,
+                                output: BINDINGS_OUTPUT,
+                                capture: true,
+                                command: [nodejs, '@INPUT@'])
+
+# link the core
+output_file = join_paths(meson.source_root(), 'lib', COMPILER_OUTPUT)
+compiler_output = custom_target(COMPILER_OUTPUT,
+                                build_by_default: true,
+                                input: core_objects + shared_objects,
+                                output: COMPILER_OUTPUT,
+                                depend_files: [ JS_LIBRARY ],
+                                command: [
+                                  emcc, '-o', output_file,
+                                  '--post-js', bindings_output,
+                                  '--js-library', JS_LIBRARY,
+                                  '@INPUT@'
+                                ] + compiler_args + optional_args)
Index: meson_options.txt
===================================================================
new file mode 100644
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,2 @@
+option('tracing', type: 'boolean', value: false)
+option('emscripten-config', type: 'string', value: '~/.emscripten')
